From bbb22479a8aefd1b040d236dde395c0220ed4bc4 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 25 Dec 2022 21:32:47 +0100 Subject: [PATCH 001/128] Add "ModBubbles" for the osu ruleset. --- .../Mods/TestSceneOsuModBubbles.cs | 19 ++ osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 228 ++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 3 + 4 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModBubbles.cs create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModBubbles.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModBubbles.cs new file mode 100644 index 0000000000..e72a1f79f5 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModBubbles.cs @@ -0,0 +1,19 @@ +// 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.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModBubbles : OsuModTestScene + { + [Test] + public void TestOsuModBubbles() => CreateModTest(new ModTestData + { + Mod = new OsuModBubbles(), + Autoplay = true, + PassCondition = () => true + }); + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs new file mode 100644 index 0000000000..c51ebde383 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -0,0 +1,228 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Pooling; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public partial class OsuModBubbles : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset, IApplicableToScoreProcessor + { + public override string Name => "Bubbles"; + + public override string Acronym => "BB"; + + public override LocalisableString Description => "Dont let their popping distract you!"; + + public override double ScoreMultiplier => 1; + + public override ModType Type => ModType.Fun; + + // Compatibility with these seems potentially feasible in the future, blocked for now because they dont work as one would expect + public override Type[] IncompatibleMods => new[] { typeof(OsuModBarrelRoll), typeof(OsuModMagnetised), typeof(OsuModRepel) }; + + private PlayfieldAdjustmentContainer adjustmentContainer = null!; + private BubbleContainer bubbleContainer = null!; + + private readonly Bindable currentCombo = new BindableInt(); + + private float maxSize; + private float bubbleRadius; + private double bubbleFade; + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + currentCombo.BindTo(scoreProcessor.Combo); + currentCombo.BindValueChanged(combo => + maxSize = Math.Min(1.75f, (float)(1.25 + 0.005 * combo.NewValue)), true); + } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Multiplying by 2 results in an initial size that is too large, hence 1.85 has been chosen + bubbleRadius = (float)(drawableRuleset.Beatmap.HitObjects.OfType().First().Radius * 1.85f); + bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType().First().TimeFadeIn * 2; + + // We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering) + drawableRuleset.Playfield.DisplayJudgements.Value = false; + + adjustmentContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer(); + + adjustmentContainer.Add(bubbleContainer = new BubbleContainer()); + drawableRuleset.KeyBindingInputManager.Add(adjustmentContainer); + } + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyBubbleState(hitObject); + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyBubbleState(hitObject); + + private void applyBubbleState(DrawableHitObject drawableObject) + { + if (drawableObject is not DrawableOsuHitObject drawableOsuObject || !drawableObject.Judged) return; + + OsuHitObject hitObject = drawableOsuObject.HitObject; + + switch (drawableOsuObject) + { + //Needs to be done explicitly to avoid being handled by DrawableHitCircle below + case DrawableSliderHead: + addBubbleContainer(hitObject.Position); + break; + + //Stack leniency causes placement issues if this isn't handled as such. + case DrawableHitCircle hitCircle: + addBubbleContainer(hitCircle.Position); + break; + + case DrawableSpinnerTick: + case DrawableSlider: + return; + + default: + addBubbleContainer(hitObject.Position); + break; + } + + void addBubbleContainer(Vector2 position) => bubbleContainer.Add(new BubbleLifeTimeEntry + { + LifetimeStart = bubbleContainer.Time.Current, + Colour = drawableOsuObject.AccentColour.Value, + Position = position, + InitialSize = new Vector2(bubbleRadius), + MaxSize = maxSize, + FadeTime = bubbleFade, + IsHit = drawableOsuObject.IsHit + } + ); + } + + #region Pooled Bubble drawable + + //LifetimeEntry flow is necessary to allow for correct rewind behaviour, can probably be made generic later if more mods are made requiring it + //Todo: find solution to bubbles rewinding in "groups" + private sealed partial class BubbleContainer : PooledDrawableWithLifetimeContainer + { + protected override bool RemoveRewoundEntry => true; + + private readonly DrawablePool pool; + + public BubbleContainer() + { + RelativeSizeAxes = Axes.Both; + AddInternal(pool = new DrawablePool(10, 1000)); + } + + protected override BubbleObject GetDrawable(BubbleLifeTimeEntry entry) => pool.Get(d => d.Apply(entry)); + } + + private sealed partial class BubbleObject : PoolableDrawableWithLifetime + { + private readonly BubbleDrawable bubbleDrawable; + + public BubbleObject() + { + InternalChild = bubbleDrawable = new BubbleDrawable(); + } + + protected override void OnApply(BubbleLifeTimeEntry entry) + { + base.OnApply(entry); + if (IsLoaded) + apply(entry); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + apply(Entry); + } + + private void apply(BubbleLifeTimeEntry? entry) + { + if (entry == null) + return; + + ApplyTransformsAt(float.MinValue, true); + ClearTransforms(true); + + Position = entry.Position; + + bubbleDrawable.Animate(entry); + + LifetimeEnd = bubbleDrawable.LatestTransformEndTime; + } + } + + private partial class BubbleDrawable : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new Circle + { + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Colour4.Black.Opacity(0.05f), + Type = EdgeEffectType.Shadow, + Radius = 5 + } + }; + } + + public void Animate(BubbleLifeTimeEntry entry) + { + Size = entry.InitialSize; + this + .ScaleTo(entry.MaxSize, entry.FadeTime * 6, Easing.OutSine) + .FadeColour(entry.IsHit ? entry.Colour : Colour4.Black, entry.FadeTime, Easing.OutSine) + .Delay(entry.FadeTime) + .FadeColour(entry.IsHit ? entry.Colour.Darken(.4f) : Colour4.Black, entry.FadeTime * 5, Easing.OutSine) + .Then() + .ScaleTo(entry.MaxSize * 1.5f, entry.FadeTime, Easing.OutSine) + .FadeTo(0, entry.FadeTime, Easing.OutQuint); + } + } + + private class BubbleLifeTimeEntry : LifetimeEntry + { + public Vector2 InitialSize { get; set; } + + public float MaxSize { get; set; } + + public Vector2 Position { get; set; } + + public Colour4 Colour { get; set; } + + // FadeTime is based on the approach rate of the beatmap. + public double FadeTime { get; set; } + + // Whether the corresponding HitObject was hit + public bool IsHit { get; set; } + } + + #endregion + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 79a566e33c..0df1e4dfca 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -202,7 +202,8 @@ namespace osu.Game.Rulesets.Osu new OsuModNoScope(), new MultiMod(new OsuModMagnetised(), new OsuModRepel()), new ModAdaptiveSpeed(), - new OsuModFreezeFrame() + new OsuModFreezeFrame(), + new OsuModBubbles() }; case ModType.System: diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 45fa55c7f2..2c9ef357b5 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -83,6 +83,9 @@ namespace osu.Game.Rulesets.Mods flashlight.Combo.BindTo(Combo); drawableRuleset.KeyBindingInputManager.Add(flashlight); + + // Stop flashlight from being drawn underneath other mods that generate HitObjects. + drawableRuleset.KeyBindingInputManager.ChangeChildDepth(flashlight, -1); } protected abstract Flashlight CreateFlashlight(); From 8a108b143e10bf80162eb0109aad7a68ae9692fc Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 25 Dec 2022 21:33:10 +0100 Subject: [PATCH 002/128] Address mod incompatibilities --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 3 +++ osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 17 +++++++++++------ osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 9e71f657ce..2394cf92fc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.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.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -10,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModBarrelRoll : ModBarrelRoll, IApplicableToDrawableHitObject { + public override Type[] IncompatibleMods => new[] { typeof(OsuModBubbles) }; + public void ApplyToDrawableHitObject(DrawableHitObject d) { d.OnUpdate += _ => diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index c51ebde383..2e4d574148 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -86,12 +86,12 @@ namespace osu.Game.Rulesets.Osu.Mods { //Needs to be done explicitly to avoid being handled by DrawableHitCircle below case DrawableSliderHead: - addBubbleContainer(hitObject.Position); + addBubbleContainer(hitObject.Position, drawableOsuObject); break; //Stack leniency causes placement issues if this isn't handled as such. case DrawableHitCircle hitCircle: - addBubbleContainer(hitCircle.Position); + addBubbleContainer(hitCircle.Position, drawableOsuObject); break; case DrawableSpinnerTick: @@ -99,19 +99,24 @@ namespace osu.Game.Rulesets.Osu.Mods return; default: - addBubbleContainer(hitObject.Position); + addBubbleContainer(hitObject.Position, drawableOsuObject); break; } + } - void addBubbleContainer(Vector2 position) => bubbleContainer.Add(new BubbleLifeTimeEntry + private void addBubbleContainer(Vector2 position, DrawableHitObject hitObject) + { + bubbleContainer.Add + ( + new BubbleLifeTimeEntry { LifetimeStart = bubbleContainer.Time.Current, - Colour = drawableOsuObject.AccentColour.Value, + Colour = hitObject.AccentColour.Value, Position = position, InitialSize = new Vector2(bubbleRadius), MaxSize = maxSize, FadeTime = bubbleFade, - IsHit = drawableOsuObject.IsHit + IsHit = hitObject.IsHit } ); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 38d90eb121..c8c4cd6a14 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) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles) }; [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/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 31a6b69d6b..28d459cedb 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) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles) }; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) From ca84b885dcc6695cb7da3549757f7e6338bc37de Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 11 Jan 2023 17:51:41 +0100 Subject: [PATCH 003/128] Add more detail to bubbles --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 58 ++++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 2e4d574148..f5e7e035b2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Pooling; @@ -21,6 +22,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -178,21 +180,38 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private partial class BubbleDrawable : CompositeDrawable + private partial class BubbleDrawable : CompositeDrawable, IHasAccentColour { + public Color4 AccentColour { get; set; } + + private Circle outerCircle = null!; + private Circle innerCircle = null!; + [BackgroundDependencyLoader] private void load() { - InternalChild = new Circle + InternalChildren = new Drawable[] { - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters + outerCircle = new Circle { - Colour = Colour4.Black.Opacity(0.05f), - Type = EdgeEffectType.Shadow, - Radius = 5 + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + MaskingSmoothness = 2, + BorderThickness = 0, + BorderColour = Colour4.Transparent, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 3, + Colour = Colour4.Black.Opacity(0.05f) + } + }, + innerCircle = new Circle + { + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.5f) } }; } @@ -202,12 +221,25 @@ namespace osu.Game.Rulesets.Osu.Mods Size = entry.InitialSize; this .ScaleTo(entry.MaxSize, entry.FadeTime * 6, Easing.OutSine) - .FadeColour(entry.IsHit ? entry.Colour : Colour4.Black, entry.FadeTime, Easing.OutSine) - .Delay(entry.FadeTime) - .FadeColour(entry.IsHit ? entry.Colour.Darken(.4f) : Colour4.Black, entry.FadeTime * 5, Easing.OutSine) .Then() .ScaleTo(entry.MaxSize * 1.5f, entry.FadeTime, Easing.OutSine) - .FadeTo(0, entry.FadeTime, Easing.OutQuint); + .FadeTo(0, entry.FadeTime, Easing.OutExpo); + + animateCircles(entry); + } + + private void animateCircles(BubbleLifeTimeEntry entry) + { + innerCircle.FadeColour(entry.IsHit ? entry.Colour.Darken(0.2f) : Colour4.Black) + .FadeColour(entry.IsHit ? entry.Colour.Lighten(0.2f) : Colour4.Black, entry.FadeTime * 7); + + innerCircle.FadeTo(0.5f, entry.FadeTime * 7, Easing.InExpo) + .ScaleTo(1.1f, entry.FadeTime * 7, Easing.InSine); + + outerCircle + .FadeColour(entry.IsHit ? entry.Colour : Colour4.Black, entry.FadeTime, Easing.OutSine) + .Delay(entry.FadeTime) + .FadeColour(entry.IsHit ? entry.Colour.Darken(.4f) : Colour4.Black, entry.FadeTime * 5, Easing.OutSine); } } From 7c81f1e75bb40981a55f16a3544d9521280bf49c Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Fri, 27 Jan 2023 11:21:11 +0100 Subject: [PATCH 004/128] Remove unnecessary BDL from bubble drawable Improve animation duration formula --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 85 +++++++++------------ 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index f5e7e035b2..6613d84e0e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,7 +11,6 @@ using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; -using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Pooling; @@ -22,7 +20,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -63,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods { // Multiplying by 2 results in an initial size that is too large, hence 1.85 has been chosen bubbleRadius = (float)(drawableRuleset.Beatmap.HitObjects.OfType().First().Radius * 1.85f); - bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType().First().TimeFadeIn * 2; + bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType().First().TimePreempt * 2; // We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering) drawableRuleset.Playfield.DisplayJudgements.Value = false; @@ -125,8 +122,8 @@ namespace osu.Game.Rulesets.Osu.Mods #region Pooled Bubble drawable - //LifetimeEntry flow is necessary to allow for correct rewind behaviour, can probably be made generic later if more mods are made requiring it - //Todo: find solution to bubbles rewinding in "groups" + // LifetimeEntry flow is necessary to allow for correct rewind behaviour, can probably be made generic later if more mods are made requiring it + // Todo: find solution to bubbles rewinding in "groups" private sealed partial class BubbleContainer : PooledDrawableWithLifetimeContainer { protected override bool RemoveRewoundEntry => true; @@ -166,8 +163,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void apply(BubbleLifeTimeEntry? entry) { - if (entry == null) - return; + if (entry == null) return; ApplyTransformsAt(float.MinValue, true); ClearTransforms(true); @@ -180,38 +176,36 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private partial class BubbleDrawable : CompositeDrawable, IHasAccentColour + private partial class BubbleDrawable : CircularContainer { - public Color4 AccentColour { get; set; } + private readonly Circle innerCircle; + private readonly Box colourBox; - private Circle outerCircle = null!; - private Circle innerCircle = null!; - - [BackgroundDependencyLoader] - private void load() + public BubbleDrawable() { - InternalChildren = new Drawable[] + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Masking = true; + MaskingSmoothness = 2; + BorderThickness = 0; + BorderColour = Colour4.Transparent; + EdgeEffect = new EdgeEffectParameters { - outerCircle = new Circle - { - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - MaskingSmoothness = 2, - BorderThickness = 0, - BorderColour = Colour4.Transparent, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 3, - Colour = Colour4.Black.Opacity(0.05f) - } - }, + Type = EdgeEffectType.Shadow, + Radius = 3, + Colour = Colour4.Black.Opacity(0.05f) + }; + + Children = new Drawable[] + { + colourBox = new Box { RelativeSizeAxes = Axes.Both, }, innerCircle = new Circle { + Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.5f) + Size = new Vector2(0.5f), } }; } @@ -219,27 +213,24 @@ namespace osu.Game.Rulesets.Osu.Mods public void Animate(BubbleLifeTimeEntry entry) { Size = entry.InitialSize; - this - .ScaleTo(entry.MaxSize, entry.FadeTime * 6, Easing.OutSine) + + this.ScaleTo(entry.MaxSize, getAnimationTime() * 0.8f, Easing.OutSine) .Then() - .ScaleTo(entry.MaxSize * 1.5f, entry.FadeTime, Easing.OutSine) - .FadeTo(0, entry.FadeTime, Easing.OutExpo); + .ScaleTo(entry.MaxSize * 1.5f, getAnimationTime() * 0.2f, Easing.OutSine) + .FadeTo(0, getAnimationTime() * 0.2f, Easing.OutExpo); - animateCircles(entry); - } + colourBox.FadeColour(entry.IsHit ? entry.Colour : Colour4.Black, getAnimationTime() * 0.1f, Easing.OutSine) + .Then() + .FadeColour(entry.IsHit ? entry.Colour.Darken(0.4f) : Colour4.Black, getAnimationTime() * 0.9f, Easing.OutSine); - private void animateCircles(BubbleLifeTimeEntry entry) - { innerCircle.FadeColour(entry.IsHit ? entry.Colour.Darken(0.2f) : Colour4.Black) - .FadeColour(entry.IsHit ? entry.Colour.Lighten(0.2f) : Colour4.Black, entry.FadeTime * 7); + .FadeColour(entry.IsHit ? entry.Colour.Lighten(0.2f) : Colour4.Black, getAnimationTime()); - innerCircle.FadeTo(0.5f, entry.FadeTime * 7, Easing.InExpo) - .ScaleTo(1.1f, entry.FadeTime * 7, Easing.InSine); + innerCircle.FadeTo(0.5f, getAnimationTime(), Easing.InExpo) + .ScaleTo(2.2f, getAnimationTime(), Easing.InSine); - outerCircle - .FadeColour(entry.IsHit ? entry.Colour : Colour4.Black, entry.FadeTime, Easing.OutSine) - .Delay(entry.FadeTime) - .FadeColour(entry.IsHit ? entry.Colour.Darken(.4f) : Colour4.Black, entry.FadeTime * 5, Easing.OutSine); + // The absolute length of the bubble's animation, can be used in fractions for animations of partial length + double getAnimationTime() => 2000 + 450 / (450 / entry.FadeTime); } } From c3090dea5f7bea51e2662cd82fc292d65c56d7ca Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 28 Jan 2023 00:30:30 +0100 Subject: [PATCH 005/128] Simplify animations --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 28 +++++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 6613d84e0e..479741b5b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -214,23 +214,29 @@ namespace osu.Game.Rulesets.Osu.Mods { Size = entry.InitialSize; - this.ScaleTo(entry.MaxSize, getAnimationTime() * 0.8f, Easing.OutSine) + //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. + var colourDarker = entry.Colour.Darken(0.1f); + + this.ScaleTo(entry.MaxSize, getAnimationDuration() * 0.8f, Easing.OutSine) .Then() - .ScaleTo(entry.MaxSize * 1.5f, getAnimationTime() * 0.2f, Easing.OutSine) - .FadeTo(0, getAnimationTime() * 0.2f, Easing.OutExpo); + .ScaleTo(entry.MaxSize * 1.5f, getAnimationDuration() * 0.2f, Easing.OutSine) + .FadeTo(0, getAnimationDuration() * 0.2f, Easing.OutExpo); - colourBox.FadeColour(entry.IsHit ? entry.Colour : Colour4.Black, getAnimationTime() * 0.1f, Easing.OutSine) - .Then() - .FadeColour(entry.IsHit ? entry.Colour.Darken(0.4f) : Colour4.Black, getAnimationTime() * 0.9f, Easing.OutSine); + innerCircle.ScaleTo(2f, getAnimationDuration() * 0.8f, Easing.OutCubic); - innerCircle.FadeColour(entry.IsHit ? entry.Colour.Darken(0.2f) : Colour4.Black) - .FadeColour(entry.IsHit ? entry.Colour.Lighten(0.2f) : Colour4.Black, getAnimationTime()); + if (!entry.IsHit) + { + colourBox.Colour = Colour4.Black; + innerCircle.Colour = Colour4.Black; - innerCircle.FadeTo(0.5f, getAnimationTime(), Easing.InExpo) - .ScaleTo(2.2f, getAnimationTime(), Easing.InSine); + return; + } + + colourBox.FadeColour(colourDarker, getAnimationDuration() * 0.2f, Easing.OutQuint); + innerCircle.FadeColour(colourDarker); // The absolute length of the bubble's animation, can be used in fractions for animations of partial length - double getAnimationTime() => 2000 + 450 / (450 / entry.FadeTime); + double getAnimationDuration() => 1700 + Math.Pow(entry.FadeTime, 1.15f); } } From 66da4c0288fd63d87fa7b4ea3547da39796e74e7 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 28 Jan 2023 17:38:24 +0100 Subject: [PATCH 006/128] Add colouration to the sliders to better match the vibrancy of the mod --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 27 +++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 479741b5b9..0101427f7a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -77,6 +78,12 @@ namespace osu.Game.Rulesets.Osu.Mods private void applyBubbleState(DrawableHitObject drawableObject) { + if (drawableObject is DrawableSlider slider) + { + slider.Body.OnSkinChanged += () => applySliderState(slider); + applySliderState(slider); + } + if (drawableObject is not DrawableOsuHitObject drawableOsuObject || !drawableObject.Judged) return; OsuHitObject hitObject = drawableOsuObject.HitObject; @@ -93,9 +100,9 @@ namespace osu.Game.Rulesets.Osu.Mods addBubbleContainer(hitCircle.Position, drawableOsuObject); break; - case DrawableSpinnerTick: case DrawableSlider: - return; + case DrawableSpinnerTick: + break; default: addBubbleContainer(hitObject.Position, drawableOsuObject); @@ -103,6 +110,9 @@ namespace osu.Game.Rulesets.Osu.Mods } } + private void applySliderState(DrawableSlider slider) => + ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; + private void addBubbleContainer(Vector2 position, DrawableHitObject hitObject) { bubbleContainer.Add @@ -217,12 +227,12 @@ namespace osu.Game.Rulesets.Osu.Mods //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. var colourDarker = entry.Colour.Darken(0.1f); - this.ScaleTo(entry.MaxSize, getAnimationDuration() * 0.8f, Easing.OutSine) + this.ScaleTo(entry.MaxSize, getAnimationDuration() * 0.8f) .Then() - .ScaleTo(entry.MaxSize * 1.5f, getAnimationDuration() * 0.2f, Easing.OutSine) - .FadeTo(0, getAnimationDuration() * 0.2f, Easing.OutExpo); + .ScaleTo(entry.MaxSize * 1.5f, getAnimationDuration() * 0.2f, Easing.OutQuint) + .FadeTo(0, getAnimationDuration() * 0.2f, Easing.OutQuint); - innerCircle.ScaleTo(2f, getAnimationDuration() * 0.8f, Easing.OutCubic); + innerCircle.ScaleTo(2f, getAnimationDuration() * 0.8f, Easing.OutQuint); if (!entry.IsHit) { @@ -232,11 +242,12 @@ namespace osu.Game.Rulesets.Osu.Mods return; } - colourBox.FadeColour(colourDarker, getAnimationDuration() * 0.2f, Easing.OutQuint); + colourBox.FadeColour(colourDarker, getAnimationDuration() * 0.2f, Easing.OutQuint + ); innerCircle.FadeColour(colourDarker); // The absolute length of the bubble's animation, can be used in fractions for animations of partial length - double getAnimationDuration() => 1700 + Math.Pow(entry.FadeTime, 1.15f); + double getAnimationDuration() => 1700 + Math.Pow(entry.FadeTime, 1.07f); } } From 3bdf83bf44e63000d2a4c23c7467a1aa24b87724 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 28 Jan 2023 22:44:57 +0100 Subject: [PATCH 007/128] Redo the drawable structure of bubbledrawable to run and look better --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 50 ++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 0101427f7a..2c90bfa399 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Performance; @@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Mods //Needs to be done explicitly to avoid being handled by DrawableHitCircle below case DrawableSliderHead: addBubbleContainer(hitObject.Position, drawableOsuObject); - break; + return; //Stack leniency causes placement issues if this isn't handled as such. case DrawableHitCircle hitCircle: @@ -188,7 +189,6 @@ namespace osu.Game.Rulesets.Osu.Mods private partial class BubbleDrawable : CircularContainer { - private readonly Circle innerCircle; private readonly Box colourBox; public BubbleDrawable() @@ -196,55 +196,55 @@ namespace osu.Game.Rulesets.Osu.Mods Anchor = Anchor.Centre; Origin = Anchor.Centre; - Masking = true; MaskingSmoothness = 2; BorderThickness = 0; - BorderColour = Colour4.Transparent; + BorderColour = Colour4.White; + Masking = true; EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Radius = 3, Colour = Colour4.Black.Opacity(0.05f) }; - - Children = new Drawable[] - { - colourBox = new Box { RelativeSizeAxes = Axes.Both, }, - innerCircle = new Circle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.5f), - } - }; + Child = colourBox = new Box { RelativeSizeAxes = Axes.Both, }; } public void Animate(BubbleLifeTimeEntry entry) { Size = entry.InitialSize; + BorderThickness = Width / 3.5f; //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. - var colourDarker = entry.Colour.Darken(0.1f); + ColourInfo colourDarker = entry.Colour.Darken(0.1f); + // Main bubble scaling based on combo this.ScaleTo(entry.MaxSize, getAnimationDuration() * 0.8f) .Then() + // Pop at the end of the bubbles life time .ScaleTo(entry.MaxSize * 1.5f, getAnimationDuration() * 0.2f, Easing.OutQuint) - .FadeTo(0, getAnimationDuration() * 0.2f, Easing.OutQuint); - - innerCircle.ScaleTo(2f, getAnimationDuration() * 0.8f, Easing.OutQuint); + .FadeTo(0, getAnimationDuration() * 0.2f, Easing.OutCirc); if (!entry.IsHit) { - colourBox.Colour = Colour4.Black; - innerCircle.Colour = Colour4.Black; + Colour = Colour4.Black; + BorderColour = Colour4.Black; return; } - colourBox.FadeColour(colourDarker, getAnimationDuration() * 0.2f, Easing.OutQuint - ); - innerCircle.FadeColour(colourDarker); + colourBox.FadeColour(colourDarker); + + this.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration() * 0.3f, Easing.OutQuint); + + // Ripple effect utilises the border to reduce drawable count + this.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration() * 0.3f, Easing.OutQuint) + + // Avoids transparency overlap issues during the bubble "pop" + .Then().Schedule(() => + { + BorderThickness = 0; + BorderColour = Colour4.Transparent; + }); // The absolute length of the bubble's animation, can be used in fractions for animations of partial length double getAnimationDuration() => 1700 + Math.Pow(entry.FadeTime, 1.07f); From abcb564a74efeb3bb9e43ee1686f402297d416b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Feb 2023 17:32:17 +0900 Subject: [PATCH 008/128] Code quality pass of `OsuModBubbles` --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 24 ++++++--------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 2c90bfa399..3606434042 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -29,15 +29,15 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Bubbles"; - public override string Acronym => "BB"; + public override string Acronym => "BU"; - public override LocalisableString Description => "Dont let their popping distract you!"; + public override LocalisableString Description => "Don't let their popping distract you!"; public override double ScoreMultiplier => 1; public override ModType Type => ModType.Fun; - // Compatibility with these seems potentially feasible in the future, blocked for now because they dont work as one would expect + // Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect public override Type[] IncompatibleMods => new[] { typeof(OsuModBarrelRoll), typeof(OsuModMagnetised), typeof(OsuModRepel) }; private PlayfieldAdjustmentContainer adjustmentContainer = null!; @@ -87,26 +87,14 @@ namespace osu.Game.Rulesets.Osu.Mods if (drawableObject is not DrawableOsuHitObject drawableOsuObject || !drawableObject.Judged) return; - OsuHitObject hitObject = drawableOsuObject.HitObject; - switch (drawableOsuObject) { - //Needs to be done explicitly to avoid being handled by DrawableHitCircle below - case DrawableSliderHead: - addBubbleContainer(hitObject.Position, drawableOsuObject); - return; - - //Stack leniency causes placement issues if this isn't handled as such. - case DrawableHitCircle hitCircle: - addBubbleContainer(hitCircle.Position, drawableOsuObject); - break; - case DrawableSlider: case DrawableSpinnerTick: break; default: - addBubbleContainer(hitObject.Position, drawableOsuObject); + addBubbleForObject(drawableOsuObject); break; } } @@ -114,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void applySliderState(DrawableSlider slider) => ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; - private void addBubbleContainer(Vector2 position, DrawableHitObject hitObject) + private void addBubbleForObject(DrawableOsuHitObject hitObject) { bubbleContainer.Add ( @@ -122,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Mods { LifetimeStart = bubbleContainer.Time.Current, Colour = hitObject.AccentColour.Value, - Position = position, + Position = hitObject.HitObject.Position, InitialSize = new Vector2(bubbleRadius), MaxSize = maxSize, FadeTime = bubbleFade, From f0d4b9f0ca339c79c74fba39f1d9a97de37f5f6e Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 6 Feb 2023 17:00:47 +0100 Subject: [PATCH 009/128] Add inline comment for colour border override --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 3606434042..41430bb323 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -99,6 +99,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } + // Makes the slider border coloured on all skins private void applySliderState(DrawableSlider slider) => ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; From 5e0c4aa904f28435bb2ffad3967f2f4fe2b08802 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 8 Feb 2023 11:12:14 +0100 Subject: [PATCH 010/128] Refactor pooling for bubbles, tweak the animations a tad, add some clarifying comments --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 231 ++++++++++---------- 1 file changed, 114 insertions(+), 117 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 41430bb323..8cf9c619d7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -8,13 +8,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning.Default; @@ -41,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModBarrelRoll), typeof(OsuModMagnetised), typeof(OsuModRepel) }; private PlayfieldAdjustmentContainer adjustmentContainer = null!; - private BubbleContainer bubbleContainer = null!; + private Container bubbleContainer = null!; private readonly Bindable currentCombo = new BindableInt(); @@ -49,6 +47,10 @@ namespace osu.Game.Rulesets.Osu.Mods private float bubbleRadius; private double bubbleFade; + private readonly DrawablePool bubblePool = new DrawablePool(100); + + private DrawableOsuHitObject lastJudgedHitobject = null!; + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) @@ -56,6 +58,63 @@ namespace osu.Game.Rulesets.Osu.Mods currentCombo.BindTo(scoreProcessor.Combo); currentCombo.BindValueChanged(combo => maxSize = Math.Min(1.75f, (float)(1.25 + 0.005 * combo.NewValue)), true); + + scoreProcessor.NewJudgement += result => + { + if (result.HitObject is not OsuHitObject osuHitObject) return; + + DrawableOsuHitObject drawableOsuHitObject = lastJudgedHitobject; + + switch (result.HitObject) + { + case Slider: + case SpinnerTick: + break; + + default: + addBubble(); + break; + } + + void addBubble() + { + BubbleDrawable bubble = bubblePool.Get(); + bubble.Info = new BubbleInfo + { + InitialSize = new Vector2(bubbleRadius), + MaxSize = maxSize, + Position = getPosition(), + FadeTime = bubbleFade, + Colour = drawableOsuHitObject.AccentColour.Value, + IsHit = drawableOsuHitObject.IsHit, + }; + bubbleContainer.Add(bubble); + } + + Vector2 getPosition() + { + switch (drawableOsuHitObject) + { + // SliderHeads are derived from HitCircles, + // so we must handle them before to avoid them using the wrong positioning logic + case DrawableSliderHead: + return osuHitObject.Position; + + // Using hitobject position will cause issues with HitCircle placement due to stack leniency. + case DrawableHitCircle: + return drawableOsuHitObject.Position; + + default: + return osuHitObject.Position; + } + } + }; + + scoreProcessor.JudgementReverted += _ => + { + bubbleContainer.LastOrDefault()?.FinishTransforms(); + bubbleContainer.LastOrDefault()?.Expire(); + }; } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -69,178 +128,116 @@ namespace osu.Game.Rulesets.Osu.Mods adjustmentContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer(); - adjustmentContainer.Add(bubbleContainer = new BubbleContainer()); + adjustmentContainer.Add(bubbleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }); drawableRuleset.KeyBindingInputManager.Add(adjustmentContainer); } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyBubbleState(hitObject); - protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyBubbleState(hitObject); private void applyBubbleState(DrawableHitObject drawableObject) { + DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)drawableObject; + if (drawableObject is DrawableSlider slider) { slider.Body.OnSkinChanged += () => applySliderState(slider); applySliderState(slider); } - if (drawableObject is not DrawableOsuHitObject drawableOsuObject || !drawableObject.Judged) return; + if (osuHitObject == lastJudgedHitobject || !osuHitObject.Judged) return; - switch (drawableOsuObject) + switch (osuHitObject) { case DrawableSlider: case DrawableSpinnerTick: break; default: - addBubbleForObject(drawableOsuObject); + lastJudgedHitobject = osuHitObject; break; } } - // Makes the slider border coloured on all skins + // Makes the slider border coloured on all skins (for aesthetics) private void applySliderState(DrawableSlider slider) => ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; - private void addBubbleForObject(DrawableOsuHitObject hitObject) - { - bubbleContainer.Add - ( - new BubbleLifeTimeEntry - { - LifetimeStart = bubbleContainer.Time.Current, - Colour = hitObject.AccentColour.Value, - Position = hitObject.HitObject.Position, - InitialSize = new Vector2(bubbleRadius), - MaxSize = maxSize, - FadeTime = bubbleFade, - IsHit = hitObject.IsHit - } - ); - } - #region Pooled Bubble drawable - // LifetimeEntry flow is necessary to allow for correct rewind behaviour, can probably be made generic later if more mods are made requiring it - // Todo: find solution to bubbles rewinding in "groups" - private sealed partial class BubbleContainer : PooledDrawableWithLifetimeContainer - { - protected override bool RemoveRewoundEntry => true; - - private readonly DrawablePool pool; - - public BubbleContainer() - { - RelativeSizeAxes = Axes.Both; - AddInternal(pool = new DrawablePool(10, 1000)); - } - - protected override BubbleObject GetDrawable(BubbleLifeTimeEntry entry) => pool.Get(d => d.Apply(entry)); - } - - private sealed partial class BubbleObject : PoolableDrawableWithLifetime - { - private readonly BubbleDrawable bubbleDrawable; - - public BubbleObject() - { - InternalChild = bubbleDrawable = new BubbleDrawable(); - } - - protected override void OnApply(BubbleLifeTimeEntry entry) - { - base.OnApply(entry); - if (IsLoaded) - apply(entry); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - apply(Entry); - } - - private void apply(BubbleLifeTimeEntry? entry) - { - if (entry == null) return; - - ApplyTransformsAt(float.MinValue, true); - ClearTransforms(true); - - Position = entry.Position; - - bubbleDrawable.Animate(entry); - - LifetimeEnd = bubbleDrawable.LatestTransformEndTime; - } - } - - private partial class BubbleDrawable : CircularContainer + private partial class BubbleDrawable : PoolableDrawable { private readonly Box colourBox; + private readonly CircularContainer content; + + public BubbleInfo Info { get; set; } public BubbleDrawable() { - Anchor = Anchor.Centre; Origin = Anchor.Centre; - - MaskingSmoothness = 2; - BorderThickness = 0; - BorderColour = Colour4.White; - Masking = true; - EdgeEffect = new EdgeEffectParameters + InternalChild = content = new CircularContainer { - Type = EdgeEffectType.Shadow, - Radius = 3, - Colour = Colour4.Black.Opacity(0.05f) + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + MaskingSmoothness = 2, + BorderThickness = 0, + BorderColour = Colour4.White, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 3, + Colour = Colour4.Black.Opacity(0.05f), + }, + Child = colourBox = new Box { RelativeSizeAxes = Axes.Both, } }; - Child = colourBox = new Box { RelativeSizeAxes = Axes.Both, }; } - public void Animate(BubbleLifeTimeEntry entry) + protected override void PrepareForUse() { - Size = entry.InitialSize; - BorderThickness = Width / 3.5f; + Alpha = 1; + Colour = Colour4.White; + Scale = new Vector2(1); + Position = Info.Position; + Size = Info.InitialSize; + content.BorderThickness = Info.InitialSize.X / 3.5f; + content.BorderColour = Colour4.White; //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. - ColourInfo colourDarker = entry.Colour.Darken(0.1f); + ColourInfo colourDarker = Info.Colour.Darken(0.1f); // Main bubble scaling based on combo - this.ScaleTo(entry.MaxSize, getAnimationDuration() * 0.8f) + this.ScaleTo(Info.MaxSize, getAnimationDuration() * 0.8f) .Then() // Pop at the end of the bubbles life time - .ScaleTo(entry.MaxSize * 1.5f, getAnimationDuration() * 0.2f, Easing.OutQuint) - .FadeTo(0, getAnimationDuration() * 0.2f, Easing.OutCirc); + .ScaleTo(Info.MaxSize * 1.5f, getAnimationDuration() * 0.2f, Easing.OutQuint) + .FadeOutFromOne(getAnimationDuration() * 0.2f, Easing.OutCirc).Expire(); - if (!entry.IsHit) + if (Info.IsHit) { - Colour = Colour4.Black; - BorderColour = Colour4.Black; + colourBox.FadeColour(colourDarker); + + content.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration() * 0.3f, Easing.OutQuint); + // Ripple effect utilises the border to reduce drawable count + content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration() * 0.3f, Easing.OutQuint) + // Avoids transparency overlap issues during the bubble "pop" + .Then().Schedule(() => content.BorderThickness = 0); return; } - colourBox.FadeColour(colourDarker); - - this.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration() * 0.3f, Easing.OutQuint); - - // Ripple effect utilises the border to reduce drawable count - this.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration() * 0.3f, Easing.OutQuint) - - // Avoids transparency overlap issues during the bubble "pop" - .Then().Schedule(() => - { - BorderThickness = 0; - BorderColour = Colour4.Transparent; - }); + Colour = Colour4.Black; // The absolute length of the bubble's animation, can be used in fractions for animations of partial length - double getAnimationDuration() => 1700 + Math.Pow(entry.FadeTime, 1.07f); + double getAnimationDuration() => 1700 + Math.Pow(Info.FadeTime, 1.07f); } } - private class BubbleLifeTimeEntry : LifetimeEntry + private struct BubbleInfo { public Vector2 InitialSize { get; set; } From 6ff6e06a69256987d7a409a154c021df9988df30 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 12 Feb 2023 11:37:07 +0100 Subject: [PATCH 011/128] Simplify bubble container structure, modify some comments --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 8cf9c619d7..3e3fce5c27 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -38,8 +38,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect public override Type[] IncompatibleMods => new[] { typeof(OsuModBarrelRoll), typeof(OsuModMagnetised), typeof(OsuModRepel) }; - private PlayfieldAdjustmentContainer adjustmentContainer = null!; - private Container bubbleContainer = null!; + private PlayfieldAdjustmentContainer bubbleContainer = null!; private readonly Bindable currentCombo = new BindableInt(); @@ -119,20 +118,17 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Multiplying by 2 results in an initial size that is too large, hence 1.85 has been chosen - bubbleRadius = (float)(drawableRuleset.Beatmap.HitObjects.OfType().First().Radius * 1.85f); + // Multiplying by 2 results in an initial size that is too large, hence 1.90 has been chosen + // Also avoids the HitObject bleeding around the edges of the bubble drawable at minimum size + bubbleRadius = (float)(drawableRuleset.Beatmap.HitObjects.OfType().First().Radius * 1.90f); bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType().First().TimePreempt * 2; // We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering) drawableRuleset.Playfield.DisplayJudgements.Value = false; - adjustmentContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer(); + bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer(); - adjustmentContainer.Add(bubbleContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }); - drawableRuleset.KeyBindingInputManager.Add(adjustmentContainer); + drawableRuleset.KeyBindingInputManager.Add(bubbleContainer); } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyBubbleState(hitObject); From d100a4a4915b3c67c48cf790822e93da90dc02be Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 14 Feb 2023 10:12:37 +0100 Subject: [PATCH 012/128] Make `lastJudgedHitObject` nullable, and fix typo in name. --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 23 ++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 3e3fce5c27..40c235911c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -48,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods private readonly DrawablePool bubblePool = new DrawablePool(100); - private DrawableOsuHitObject lastJudgedHitobject = null!; + private DrawableOsuHitObject? lastJudgedHitObject; public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; @@ -60,9 +62,11 @@ namespace osu.Game.Rulesets.Osu.Mods scoreProcessor.NewJudgement += result => { - if (result.HitObject is not OsuHitObject osuHitObject) return; + if (result.HitObject is not OsuHitObject osuHitObject || lastJudgedHitObject.IsNull()) return; - DrawableOsuHitObject drawableOsuHitObject = lastJudgedHitobject; + Debug.Assert(result.HitObject == lastJudgedHitObject.HitObject); + + DrawableOsuHitObject drawableOsuHitObject = lastJudgedHitObject; switch (result.HitObject) { @@ -144,18 +148,9 @@ namespace osu.Game.Rulesets.Osu.Mods applySliderState(slider); } - if (osuHitObject == lastJudgedHitobject || !osuHitObject.Judged) return; + if (osuHitObject == lastJudgedHitObject || !osuHitObject.Judged) return; - switch (osuHitObject) - { - case DrawableSlider: - case DrawableSpinnerTick: - break; - - default: - lastJudgedHitobject = osuHitObject; - break; - } + lastJudgedHitObject = osuHitObject; } // Makes the slider border coloured on all skins (for aesthetics) From 2d49b5f9d66150598260451250c11736bdad87bc Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 14 Feb 2023 14:03:48 +0100 Subject: [PATCH 013/128] Move bubbles to ruleset overlays container --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 40c235911c..732626b177 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Mods bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer(); - drawableRuleset.KeyBindingInputManager.Add(bubbleContainer); + drawableRuleset.Overlays.Add(bubbleContainer); } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyBubbleState(hitObject); From 92c61c73396939363b3c6bd7ffffb083e3c74116 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 14 Feb 2023 16:31:34 +0100 Subject: [PATCH 014/128] move logic for bubble invoking to `ApplyToDrawableHitobject()`` method --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 85 +++++++++------------ 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 732626b177..4a8c11e7ff 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -2,10 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -25,7 +23,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public partial class OsuModBubbles : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset, IApplicableToScoreProcessor + public partial class OsuModBubbles : Mod, IApplicableToDrawableRuleset, IApplicableToDrawableHitObject, IApplicableToScoreProcessor { public override string Name => "Bubbles"; @@ -50,8 +48,6 @@ namespace osu.Game.Rulesets.Osu.Mods private readonly DrawablePool bubblePool = new DrawablePool(100); - private DrawableOsuHitObject? lastJudgedHitObject; - public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) @@ -60,15 +56,41 @@ namespace osu.Game.Rulesets.Osu.Mods currentCombo.BindValueChanged(combo => maxSize = Math.Min(1.75f, (float)(1.25 + 0.005 * combo.NewValue)), true); - scoreProcessor.NewJudgement += result => + scoreProcessor.JudgementReverted += _ => { - if (result.HitObject is not OsuHitObject osuHitObject || lastJudgedHitObject.IsNull()) return; + bubbleContainer.LastOrDefault()?.ClearTransforms(); + bubbleContainer.LastOrDefault()?.Expire(); + }; + } - Debug.Assert(result.HitObject == lastJudgedHitObject.HitObject); + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Multiplying by 2 results in an initial size that is too large, hence 1.90 has been chosen + // Also avoids the HitObject bleeding around the edges of the bubble drawable at minimum size + bubbleRadius = (float)(drawableRuleset.Beatmap.HitObjects.OfType().First().Radius * 1.90f); + bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType().First().TimePreempt * 2; - DrawableOsuHitObject drawableOsuHitObject = lastJudgedHitObject; + // We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering) + drawableRuleset.Playfield.DisplayJudgements.Value = false; - switch (result.HitObject) + bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer(); + + drawableRuleset.Overlays.Add(bubbleContainer); + } + + public void ApplyToDrawableHitObject(DrawableHitObject drawableObject) + { + if (drawableObject is DrawableSlider slider) + { + applySliderState(slider); + slider.Body.OnSkinChanged += () => applySliderState(slider); + } + + drawableObject.OnNewResult += (drawable, _) => + { + if (drawable is not DrawableOsuHitObject drawableOsuHitObject) return; + + switch (drawableOsuHitObject.HitObject) { case Slider: case SpinnerTick: @@ -101,56 +123,17 @@ namespace osu.Game.Rulesets.Osu.Mods // SliderHeads are derived from HitCircles, // so we must handle them before to avoid them using the wrong positioning logic case DrawableSliderHead: - return osuHitObject.Position; + return drawableOsuHitObject.HitObject.Position; // Using hitobject position will cause issues with HitCircle placement due to stack leniency. case DrawableHitCircle: return drawableOsuHitObject.Position; default: - return osuHitObject.Position; + return drawableOsuHitObject.HitObject.Position; } } }; - - scoreProcessor.JudgementReverted += _ => - { - bubbleContainer.LastOrDefault()?.FinishTransforms(); - bubbleContainer.LastOrDefault()?.Expire(); - }; - } - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - // Multiplying by 2 results in an initial size that is too large, hence 1.90 has been chosen - // Also avoids the HitObject bleeding around the edges of the bubble drawable at minimum size - bubbleRadius = (float)(drawableRuleset.Beatmap.HitObjects.OfType().First().Radius * 1.90f); - bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType().First().TimePreempt * 2; - - // We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering) - drawableRuleset.Playfield.DisplayJudgements.Value = false; - - bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer(); - - drawableRuleset.Overlays.Add(bubbleContainer); - } - - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyBubbleState(hitObject); - protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyBubbleState(hitObject); - - private void applyBubbleState(DrawableHitObject drawableObject) - { - DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)drawableObject; - - if (drawableObject is DrawableSlider slider) - { - slider.Body.OnSkinChanged += () => applySliderState(slider); - applySliderState(slider); - } - - if (osuHitObject == lastJudgedHitObject || !osuHitObject.Judged) return; - - lastJudgedHitObject = osuHitObject; } // Makes the slider border coloured on all skins (for aesthetics) From 5db624159b1230e7ca522e9ab6cdec8194e8a0fa Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 14 Feb 2023 18:06:43 +0100 Subject: [PATCH 015/128] Change bubble rewind removal to be in `ApplyToDrawableHitObject` method. --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 4a8c11e7ff..f521dfb1f8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -55,12 +55,6 @@ namespace osu.Game.Rulesets.Osu.Mods currentCombo.BindTo(scoreProcessor.Combo); currentCombo.BindValueChanged(combo => maxSize = Math.Min(1.75f, (float)(1.25 + 0.005 * combo.NewValue)), true); - - scoreProcessor.JudgementReverted += _ => - { - bubbleContainer.LastOrDefault()?.ClearTransforms(); - bubbleContainer.LastOrDefault()?.Expire(); - }; } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -134,6 +128,16 @@ namespace osu.Game.Rulesets.Osu.Mods } } }; + + drawableObject.OnRevertResult += (drawable, _) => + { + if (drawable.HitObject is SpinnerTick or Slider) return; + + BubbleDrawable? lastBubble = bubbleContainer.OfType().LastOrDefault(); + + lastBubble?.ClearTransforms(); + lastBubble?.Expire(); + }; } // Makes the slider border coloured on all skins (for aesthetics) From 82292d61621e49a158c576879ae56f47a9e52a84 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 15 Feb 2023 09:30:12 +0100 Subject: [PATCH 016/128] Make colouring for bubble more intuitive and remove unnecessary alpha assignment --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index f521dfb1f8..2a4208065d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -177,8 +177,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected override void PrepareForUse() { - Alpha = 1; - Colour = Colour4.White; + Colour = Info.IsHit ? Colour4.White : Colour4.Black; Scale = new Vector2(1); Position = Info.Position; Size = Info.InitialSize; @@ -204,12 +203,8 @@ namespace osu.Game.Rulesets.Osu.Mods content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration() * 0.3f, Easing.OutQuint) // Avoids transparency overlap issues during the bubble "pop" .Then().Schedule(() => content.BorderThickness = 0); - - return; } - Colour = Colour4.Black; - // The absolute length of the bubble's animation, can be used in fractions for animations of partial length double getAnimationDuration() => 1700 + Math.Pow(Info.FadeTime, 1.07f); } From e9a7d90273c57dbd7dfb30ff32b9ca21dc9b6d39 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 15 Feb 2023 09:33:18 +0100 Subject: [PATCH 017/128] make transform duration for bubble a method instead of a variable --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 2a4208065d..a88bf6b813 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -187,26 +187,26 @@ namespace osu.Game.Rulesets.Osu.Mods //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. ColourInfo colourDarker = Info.Colour.Darken(0.1f); + // The absolute length of the bubble's animation, can be used in fractions for animations of partial length + double getAnimationDuration = 1700 + Math.Pow(Info.FadeTime, 1.07f); + // Main bubble scaling based on combo - this.ScaleTo(Info.MaxSize, getAnimationDuration() * 0.8f) + this.ScaleTo(Info.MaxSize, getAnimationDuration * 0.8f) .Then() // Pop at the end of the bubbles life time - .ScaleTo(Info.MaxSize * 1.5f, getAnimationDuration() * 0.2f, Easing.OutQuint) - .FadeOutFromOne(getAnimationDuration() * 0.2f, Easing.OutCirc).Expire(); + .ScaleTo(Info.MaxSize * 1.5f, getAnimationDuration * 0.2f, Easing.OutQuint) + .FadeOutFromOne(getAnimationDuration * 0.2f, Easing.OutCirc).Expire(); if (Info.IsHit) { colourBox.FadeColour(colourDarker); - content.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration() * 0.3f, Easing.OutQuint); + content.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration * 0.3f, Easing.OutQuint); // Ripple effect utilises the border to reduce drawable count - content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration() * 0.3f, Easing.OutQuint) + content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration * 0.3f, Easing.OutQuint) // Avoids transparency overlap issues during the bubble "pop" .Then().Schedule(() => content.BorderThickness = 0); } - - // The absolute length of the bubble's animation, can be used in fractions for animations of partial length - double getAnimationDuration() => 1700 + Math.Pow(Info.FadeTime, 1.07f); } } From 1d1c794ccfde23743574d8579da648cf42f4e4d4 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 15 Feb 2023 09:37:47 +0100 Subject: [PATCH 018/128] Invert pointless nested `if` statement --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index a88bf6b813..d75c82dc85 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -197,16 +197,15 @@ namespace osu.Game.Rulesets.Osu.Mods .ScaleTo(Info.MaxSize * 1.5f, getAnimationDuration * 0.2f, Easing.OutQuint) .FadeOutFromOne(getAnimationDuration * 0.2f, Easing.OutCirc).Expire(); - if (Info.IsHit) - { - colourBox.FadeColour(colourDarker); + if (!Info.IsHit) return; - content.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration * 0.3f, Easing.OutQuint); - // Ripple effect utilises the border to reduce drawable count - content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration * 0.3f, Easing.OutQuint) - // Avoids transparency overlap issues during the bubble "pop" - .Then().Schedule(() => content.BorderThickness = 0); - } + colourBox.FadeColour(colourDarker); + + content.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration * 0.3f, Easing.OutQuint); + // Ripple effect utilises the border to reduce drawable count + content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration * 0.3f, Easing.OutQuint) + // Avoids transparency overlap issues during the bubble "pop" + .Then().Schedule(() => content.BorderThickness = 0); } } From 297963b461cfdfd6083784ab0c4561c43c1d4a93 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 15 Feb 2023 10:00:46 +0100 Subject: [PATCH 019/128] Remove BubbleInfo struct and consume `DrawableOsuHitObject`s directly --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 102 +++++++++----------- 1 file changed, 46 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index d75c82dc85..981932c580 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -2,8 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -98,35 +101,14 @@ namespace osu.Game.Rulesets.Osu.Mods void addBubble() { BubbleDrawable bubble = bubblePool.Get(); - bubble.Info = new BubbleInfo - { - InitialSize = new Vector2(bubbleRadius), - MaxSize = maxSize, - Position = getPosition(), - FadeTime = bubbleFade, - Colour = drawableOsuHitObject.AccentColour.Value, - IsHit = drawableOsuHitObject.IsHit, - }; + + bubble.DrawableOsuHitObject = drawableOsuHitObject; + bubble.InitialSize = new Vector2(bubbleRadius); + bubble.FadeTime = bubbleFade; + bubble.MaxSize = maxSize; + bubbleContainer.Add(bubble); } - - Vector2 getPosition() - { - switch (drawableOsuHitObject) - { - // SliderHeads are derived from HitCircles, - // so we must handle them before to avoid them using the wrong positioning logic - case DrawableSliderHead: - return drawableOsuHitObject.HitObject.Position; - - // Using hitobject position will cause issues with HitCircle placement due to stack leniency. - case DrawableHitCircle: - return drawableOsuHitObject.Position; - - default: - return drawableOsuHitObject.HitObject.Position; - } - } }; drawableObject.OnRevertResult += (drawable, _) => @@ -148,11 +130,15 @@ namespace osu.Game.Rulesets.Osu.Mods private partial class BubbleDrawable : PoolableDrawable { + public DrawableOsuHitObject? DrawableOsuHitObject { get; set; } + + public Vector2 InitialSize { get; set; } + public double FadeTime { get; set; } + public float MaxSize { get; set; } + private readonly Box colourBox; private readonly CircularContainer content; - public BubbleInfo Info { get; set; } - public BubbleDrawable() { Origin = Anchor.Centre; @@ -177,27 +163,30 @@ namespace osu.Game.Rulesets.Osu.Mods protected override void PrepareForUse() { - Colour = Info.IsHit ? Colour4.White : Colour4.Black; + Debug.Assert(DrawableOsuHitObject.IsNotNull()); + + Colour = DrawableOsuHitObject.IsHit ? Colour4.White : Colour4.Black; + Alpha = 1; Scale = new Vector2(1); - Position = Info.Position; - Size = Info.InitialSize; - content.BorderThickness = Info.InitialSize.X / 3.5f; + Position = getPosition(); + Size = InitialSize; + content.BorderThickness = InitialSize.X / 3.5f; content.BorderColour = Colour4.White; //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. - ColourInfo colourDarker = Info.Colour.Darken(0.1f); + ColourInfo colourDarker = DrawableOsuHitObject.AccentColour.Value.Darken(0.1f); // The absolute length of the bubble's animation, can be used in fractions for animations of partial length - double getAnimationDuration = 1700 + Math.Pow(Info.FadeTime, 1.07f); + double getAnimationDuration = 1700 + Math.Pow(FadeTime, 1.07f); // Main bubble scaling based on combo - this.ScaleTo(Info.MaxSize, getAnimationDuration * 0.8f) + this.ScaleTo(MaxSize, getAnimationDuration * 0.8f) .Then() // Pop at the end of the bubbles life time - .ScaleTo(Info.MaxSize * 1.5f, getAnimationDuration * 0.2f, Easing.OutQuint) - .FadeOutFromOne(getAnimationDuration * 0.2f, Easing.OutCirc).Expire(); + .ScaleTo(MaxSize * 1.5f, getAnimationDuration * 0.2f, Easing.OutQuint) + .FadeOut(getAnimationDuration * 0.2f, Easing.OutCirc).Expire(); - if (!Info.IsHit) return; + if (!DrawableOsuHitObject.IsHit) return; colourBox.FadeColour(colourDarker); @@ -206,26 +195,27 @@ namespace osu.Game.Rulesets.Osu.Mods content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration * 0.3f, Easing.OutQuint) // Avoids transparency overlap issues during the bubble "pop" .Then().Schedule(() => content.BorderThickness = 0); + + Vector2 getPosition() + { + switch (DrawableOsuHitObject) + { + // SliderHeads are derived from HitCircles, + // so we must handle them before to avoid them using the wrong positioning logic + case DrawableSliderHead: + return DrawableOsuHitObject.HitObject.Position; + + // Using hitobject position will cause issues with HitCircle placement due to stack leniency. + case DrawableHitCircle: + return DrawableOsuHitObject.Position; + + default: + return DrawableOsuHitObject.HitObject.Position; + } + } } } - private struct BubbleInfo - { - public Vector2 InitialSize { get; set; } - - public float MaxSize { get; set; } - - public Vector2 Position { get; set; } - - public Colour4 Colour { get; set; } - - // FadeTime is based on the approach rate of the beatmap. - public double FadeTime { get; set; } - - // Whether the corresponding HitObject was hit - public bool IsHit { get; set; } - } - #endregion } } From 8fc35b159f87a10a087c79c469d981ff9750bc95 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 15 Feb 2023 10:04:50 +0100 Subject: [PATCH 020/128] Remove dysfunctional slider colouring --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 981932c580..4edf726f26 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -18,7 +18,6 @@ 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.Skinning.Default; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -77,12 +76,6 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableHitObject(DrawableHitObject drawableObject) { - if (drawableObject is DrawableSlider slider) - { - applySliderState(slider); - slider.Body.OnSkinChanged += () => applySliderState(slider); - } - drawableObject.OnNewResult += (drawable, _) => { if (drawable is not DrawableOsuHitObject drawableOsuHitObject) return; @@ -122,10 +115,6 @@ namespace osu.Game.Rulesets.Osu.Mods }; } - // Makes the slider border coloured on all skins (for aesthetics) - private void applySliderState(DrawableSlider slider) => - ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; - #region Pooled Bubble drawable private partial class BubbleDrawable : PoolableDrawable From d5bc8e2941fc0a6d948fda24546cc976b12165fa Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Thu, 16 Feb 2023 11:12:30 +0100 Subject: [PATCH 021/128] Code cleanup pass: Make bubble transform logic more sane. Extract bubble `getPosition()` method. Address poorly named variables. --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 47 +++++++++++---------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 4edf726f26..0fc27c8f1d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Mods private readonly Bindable currentCombo = new BindableInt(); private float maxSize; - private float bubbleRadius; + private float bubbleSize; private double bubbleFade; private readonly DrawablePool bubblePool = new DrawablePool(100); @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Mods { // Multiplying by 2 results in an initial size that is too large, hence 1.90 has been chosen // Also avoids the HitObject bleeding around the edges of the bubble drawable at minimum size - bubbleRadius = (float)(drawableRuleset.Beatmap.HitObjects.OfType().First().Radius * 1.90f); + bubbleSize = (float)(drawableRuleset.Beatmap.HitObjects.OfType().First().Radius * 1.90f); bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType().First().TimePreempt * 2; // We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering) @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Mods BubbleDrawable bubble = bubblePool.Get(); bubble.DrawableOsuHitObject = drawableOsuHitObject; - bubble.InitialSize = new Vector2(bubbleRadius); + bubble.InitialSize = new Vector2(bubbleSize); bubble.FadeTime = bubbleFade; bubble.MaxSize = maxSize; @@ -122,9 +122,11 @@ namespace osu.Game.Rulesets.Osu.Mods public DrawableOsuHitObject? DrawableOsuHitObject { get; set; } public Vector2 InitialSize { get; set; } - public double FadeTime { get; set; } + public float MaxSize { get; set; } + public double FadeTime { get; set; } + private readonly Box colourBox; private readonly CircularContainer content; @@ -155,12 +157,9 @@ namespace osu.Game.Rulesets.Osu.Mods Debug.Assert(DrawableOsuHitObject.IsNotNull()); Colour = DrawableOsuHitObject.IsHit ? Colour4.White : Colour4.Black; - Alpha = 1; Scale = new Vector2(1); - Position = getPosition(); + Position = getPosition(DrawableOsuHitObject); Size = InitialSize; - content.BorderThickness = InitialSize.X / 3.5f; - content.BorderColour = Colour4.White; //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. ColourInfo colourDarker = DrawableOsuHitObject.AccentColour.Value.Darken(0.1f); @@ -169,7 +168,8 @@ namespace osu.Game.Rulesets.Osu.Mods double getAnimationDuration = 1700 + Math.Pow(FadeTime, 1.07f); // Main bubble scaling based on combo - this.ScaleTo(MaxSize, getAnimationDuration * 0.8f) + this.FadeTo(1) + .ScaleTo(MaxSize, getAnimationDuration * 0.8f) .Then() // Pop at the end of the bubbles life time .ScaleTo(MaxSize * 1.5f, getAnimationDuration * 0.2f, Easing.OutQuint) @@ -177,6 +177,9 @@ namespace osu.Game.Rulesets.Osu.Mods if (!DrawableOsuHitObject.IsHit) return; + content.BorderThickness = InitialSize.X / 3.5f; + content.BorderColour = Colour4.White; + colourBox.FadeColour(colourDarker); content.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration * 0.3f, Easing.OutQuint); @@ -184,23 +187,23 @@ namespace osu.Game.Rulesets.Osu.Mods content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration * 0.3f, Easing.OutQuint) // Avoids transparency overlap issues during the bubble "pop" .Then().Schedule(() => content.BorderThickness = 0); + } - Vector2 getPosition() + private Vector2 getPosition(DrawableOsuHitObject drawableOsuHitObject) + { + switch (drawableOsuHitObject) { - switch (DrawableOsuHitObject) - { - // SliderHeads are derived from HitCircles, - // so we must handle them before to avoid them using the wrong positioning logic - case DrawableSliderHead: - return DrawableOsuHitObject.HitObject.Position; + // SliderHeads are derived from HitCircles, + // so we must handle them before to avoid them using the wrong positioning logic + case DrawableSliderHead: + return drawableOsuHitObject.HitObject.Position; - // Using hitobject position will cause issues with HitCircle placement due to stack leniency. - case DrawableHitCircle: - return DrawableOsuHitObject.Position; + // Using hitobject position will cause issues with HitCircle placement due to stack leniency. + case DrawableHitCircle: + return drawableOsuHitObject.Position; - default: - return DrawableOsuHitObject.HitObject.Position; - } + default: + return drawableOsuHitObject.HitObject.Position; } } } From eaef5ff2a3e0412036246eb6b4226a156bdbeaed Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 25 Mar 2023 22:13:51 -0700 Subject: [PATCH 022/128] Fix now playing playlist not highlighting selected item on initial open --- osu.Game/Overlays/Music/PlaylistItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 00c5ce8002..4032af7651 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music selected = newSelected; updateSelectionState(false); - }); + }, true); updateSelectionState(true); }); From 46ede27869efca77ec226081454cf682ad907a77 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 28 Mar 2023 22:24:05 +0900 Subject: [PATCH 023/128] add feature to adjust `ScalingContainer` background dim --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ .../Graphics/Containers/ScalingContainer.cs | 11 +++++++++-- .../Sections/Graphics/LayoutSettings.cs | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 70ad6bfc96..a06bfcc5f0 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -157,6 +157,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Scaling, ScalingMode.Off); SetDefault(OsuSetting.SafeAreaConsiderations, true); + SetDefault(OsuSetting.ScalingMenuBackgroundDim, 0.9f, 0.5f, 1f); SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); @@ -364,6 +365,7 @@ namespace osu.Game.Configuration ScalingPositionY, ScalingSizeX, ScalingSizeY, + ScalingMenuBackgroundDim, UIScale, IntroSequence, NotifyOnUsernameMentioned, diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index fb5c3e3b60..9c6830ce05 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -46,6 +46,8 @@ namespace osu.Game.Graphics.Containers private BackgroundScreenStack backgroundStack; + private Bindable scalingMenuBackgroundDim; + private RectangleF? customRect; private bool customRectIsRelativePosition; @@ -138,6 +140,9 @@ namespace osu.Game.Graphics.Containers safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy(); safeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize)); + + scalingMenuBackgroundDim = config.GetBindable(OsuSetting.ScalingMenuBackgroundDim); + scalingMenuBackgroundDim.ValueChanged += _ => Scheduler.AddOnce(updateSize); } protected override void LoadComplete() @@ -148,7 +153,9 @@ namespace osu.Game.Graphics.Containers sizableContainer.FinishTransforms(); } - private bool requiresBackgroundVisible => (scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1); + private bool requiresBackgroundVisible => (scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays) + && (sizeX.Value != 1 || sizeY.Value != 1) + && scalingMenuBackgroundDim.Value != 1f; private void updateSize() { @@ -161,7 +168,6 @@ namespace osu.Game.Graphics.Containers { AddInternal(backgroundStack = new BackgroundScreenStack { - Colour = OsuColour.Gray(0.1f), Alpha = 0, Depth = float.MaxValue }); @@ -170,6 +176,7 @@ namespace osu.Game.Graphics.Containers } backgroundStack.FadeIn(TRANSITION_DURATION); + backgroundStack.FadeColour(OsuColour.Gray(1.0f - scalingMenuBackgroundDim.Value), 800, Easing.OutQuint); } else backgroundStack?.FadeOut(TRANSITION_DURATION); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 6465d62ef0..6dbaf27afc 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -30,6 +30,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics protected override LocalisableString Header => GraphicsSettingsStrings.LayoutHeader; private FillFlowContainer> scalingSettings = null!; + private SettingsSlider dimSlider = null!; private readonly Bindable currentDisplay = new Bindable(); private readonly IBindableList windowModes = new BindableList(); @@ -58,6 +59,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private Bindable scalingSizeX = null!; private Bindable scalingSizeY = null!; + private Bindable scalingBackgroundDim = null!; + private const int transition_duration = 400; [BackgroundDependencyLoader] @@ -71,6 +74,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); + scalingBackgroundDim = osuConfig.GetBindable(OsuSetting.ScalingMenuBackgroundDim); if (window != null) { @@ -162,6 +166,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics KeyboardStep = 0.01f, DisplayAsPercentage = true }, + dimSlider = new SettingsSlider + { + LabelText = GameplaySettingsStrings.BackgroundDim, + Current = scalingBackgroundDim, + KeyboardStep = 0.01f, + DisplayAsPercentage = true + }, } }, }; @@ -219,6 +230,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.AutoSizeAxes = scalingMode.Value != ScalingMode.Off ? Axes.Y : Axes.None; scalingSettings.ForEach(s => { + if (s == dimSlider) + { + s.TransferValueOnCommit = false; + s.CanBeShown.Value = scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays; + return; + } + s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything; s.CanBeShown.Value = scalingMode.Value != ScalingMode.Off; }); From e85c28031e2348f0f4e71eafe3d1fe13ba4305fc Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 29 Mar 2023 22:55:25 +0900 Subject: [PATCH 024/128] change weird name --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a06bfcc5f0..1e7d3cf84f 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -157,7 +157,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Scaling, ScalingMode.Off); SetDefault(OsuSetting.SafeAreaConsiderations, true); - SetDefault(OsuSetting.ScalingMenuBackgroundDim, 0.9f, 0.5f, 1f); + SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f); SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); @@ -365,7 +365,7 @@ namespace osu.Game.Configuration ScalingPositionY, ScalingSizeX, ScalingSizeY, - ScalingMenuBackgroundDim, + ScalingBackgroundDim, UIScale, IntroSequence, NotifyOnUsernameMentioned, diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 9c6830ce05..8b0450b71c 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -141,7 +141,7 @@ namespace osu.Game.Graphics.Containers safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy(); safeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize)); - scalingMenuBackgroundDim = config.GetBindable(OsuSetting.ScalingMenuBackgroundDim); + scalingMenuBackgroundDim = config.GetBindable(OsuSetting.ScalingBackgroundDim); scalingMenuBackgroundDim.ValueChanged += _ => Scheduler.AddOnce(updateSize); } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 6dbaf27afc..895ae1ed89 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); - scalingBackgroundDim = osuConfig.GetBindable(OsuSetting.ScalingMenuBackgroundDim); + scalingBackgroundDim = osuConfig.GetBindable(OsuSetting.ScalingBackgroundDim); if (window != null) { From 19b7036b95eeac979f75850feb19b2a5a1a97ce2 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 29 Mar 2023 22:56:35 +0900 Subject: [PATCH 025/128] use same same duration negligence in migrating code --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 8b0450b71c..bd52c8bb32 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -176,7 +176,7 @@ namespace osu.Game.Graphics.Containers } backgroundStack.FadeIn(TRANSITION_DURATION); - backgroundStack.FadeColour(OsuColour.Gray(1.0f - scalingMenuBackgroundDim.Value), 800, Easing.OutQuint); + backgroundStack.FadeColour(OsuColour.Gray(1.0f - scalingMenuBackgroundDim.Value), TRANSITION_DURATION, Easing.OutQuint); } else backgroundStack?.FadeOut(TRANSITION_DURATION); From 5d395e6d37f6247430e57c3ede359cad4c729b64 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 29 Mar 2023 22:59:54 +0900 Subject: [PATCH 026/128] move to ctor --- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 895ae1ed89..523b1237fa 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -171,7 +171,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = GameplaySettingsStrings.BackgroundDim, Current = scalingBackgroundDim, KeyboardStep = 0.01f, - DisplayAsPercentage = true + DisplayAsPercentage = true, + TransferValueOnCommit = false }, } }, @@ -232,7 +233,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { if (s == dimSlider) { - s.TransferValueOnCommit = false; s.CanBeShown.Value = scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays; return; } From 776c4cfaad00d1f9f99cfa70792022f68002c5b0 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 19 Apr 2023 18:34:35 -0700 Subject: [PATCH 027/128] Ensure `selected` field is correct in `updateSelectionState()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bartłomiej Dach --- osu.Game/Overlays/Music/PlaylistItem.cs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 4032af7651..4a39cc06c8 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Music var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(SelectedSet.Value, instant: true); text.AddText(@" "); // to separate the title from the artist. text.AddText(artist, sprite => @@ -66,25 +66,22 @@ namespace osu.Game.Overlays.Music sprite.Padding = new MarginPadding { Top = 1 }; }); - SelectedSet.BindValueChanged(set => - { - bool newSelected = set.NewValue?.Equals(Model) == true; - - if (newSelected == selected) - return; - - selected = newSelected; - updateSelectionState(false); - }, true); - - updateSelectionState(true); + SelectedSet.BindValueChanged(set => updateSelectionState(set.NewValue, instant: false)); + updateSelectionState(SelectedSet.Value, instant: true); }); } private bool selected; - private void updateSelectionState(bool instant) + private void updateSelectionState(Live selectedSet, bool instant) { + bool newSelected = selectedSet?.Equals(Model) == true; + + if (newSelected == selected && !instant) // instant updates should forcibly set correct state regardless of previous state. + return; + + selected = newSelected; + foreach (Drawable s in titlePart.Drawables) s.FadeColour(selected ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); } From 56ab029a58eeff34f1f976fb956b7f042d8c79df Mon Sep 17 00:00:00 2001 From: Hy0tic Date: Sat, 22 Apr 2023 13:30:08 -0400 Subject: [PATCH 028/128] fix issue where multipler does not update when adjusting speed for preset mod --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 16602db4be..d611fd3c0b 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -252,7 +252,7 @@ namespace osu.Game.Overlays.Mods if (AllowCustomisation) { - modSettingChangeTracker = new ModSettingChangeTracker(val.NewValue); + modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); } }, true); From a4c6850ab2e64fc2a195744af8b02fd6c61a4726 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 11:34:09 +0200 Subject: [PATCH 029/128] made the SampleControlPoint and DifficultyControlPoint obsolete --- .../Legacy/DistanceObjectPatternGenerator.cs | 11 ++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 32 +++++++--- .../Beatmaps/TaikoBeatmapConverter.cs | 11 ++-- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 + osu.Game/Rulesets/Objects/HitObject.cs | 61 ++++++++++++------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 15 ++++- .../Objects/Types/IHasSliderVelocity.cs | 15 +++++ 7 files changed, 105 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 2bdd0e16ad..9e031c2b4d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -49,15 +49,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy Debug.Assert(distanceData != null); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint; double beatLength; -#pragma warning disable 618 - if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint) -#pragma warning restore 618 - beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; + if (hitObject.LegacyBpmMultiplier.HasValue) + beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value; + else if (hitObject is IHasSliderVelocity hasSliderVelocity) + beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else - beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity; + beatLength = timingPoint.BeatLength; SpanCount = repeatsData?.SpanCount() ?? 1; StartTime = (int)Math.Round(hitObject.StartTime); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6c2be8a49a..6952033aec 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using Newtonsoft.Json; using osu.Framework.Caching; @@ -15,13 +16,14 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Slider : OsuHitObject, IHasPathWithRepeats + public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity { public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; @@ -134,6 +136,13 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool OnlyJudgeNestedObjects = true; + public double SliderVelocity { get; set; } = 1; + + /// + /// Whether to generate ticks on this . + /// + public bool GenerateTicks = true; + [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } @@ -151,15 +160,24 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); -#pragma warning disable 618 - var legacyDifficultyPoint = DifficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; -#pragma warning restore 618 - double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; - bool generateTicks = legacyDifficultyPoint?.GenerateTicks ?? true; + double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + } + + protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) + { + base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); + + DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; +#pragma warning disable 618 + var legacyDifficultyPoint = difficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; +#pragma warning restore 618 + + SliderVelocity = difficultyControlPoint.SliderVelocity; + GenerateTicks = legacyDifficultyPoint?.GenerateTicks ?? true; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 6a35e9376b..362ddccaf1 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -177,15 +177,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); - DifficultyControlPoint difficultyPoint = obj.DifficultyControlPoint; double beatLength; -#pragma warning disable 618 - if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint) -#pragma warning restore 618 - beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; + if (obj.LegacyBpmMultiplier.HasValue) + beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value; + else if (obj is IHasSliderVelocity hasSliderVelocity) + beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else - beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity; + beatLength = timingPoint.BeatLength; double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index a9bdd21b64..c17eea0e85 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -85,7 +85,10 @@ namespace osu.Game.Beatmaps.Formats this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList(); foreach (var hitObject in this.beatmap.HitObjects) + { + hitObject.ApplyLegacyInfo(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); + } } /// diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 25f538d211..aea564a4b9 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -16,6 +16,7 @@ using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -80,6 +81,12 @@ namespace osu.Game.Rulesets.Objects public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; + /// + /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. + /// DO NOT USE THIS UNLESS 100% SURE. + /// + public double? LegacyBpmMultiplier { get; private set; } + /// /// Whether this is in Kiai time. /// @@ -105,25 +112,6 @@ namespace osu.Game.Rulesets.Objects /// The cancellation token. public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) { - var legacyInfo = controlPointInfo as LegacyControlPointInfo; - - if (legacyInfo != null) - DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone(); - else if (ReferenceEquals(DifficultyControlPoint, DifficultyControlPoint.DEFAULT)) - DifficultyControlPoint = new DifficultyControlPoint(); - - DifficultyControlPoint.Time = StartTime; - - ApplyDefaultsToSelf(controlPointInfo, difficulty); - - // This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time. - if (legacyInfo != null) - SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone(); - else if (ReferenceEquals(SampleControlPoint, SampleControlPoint.DEFAULT)) - SampleControlPoint = new SampleControlPoint(); - - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; - nestedHitObjects.Clear(); CreateNestedHitObjects(cancellationToken); @@ -164,9 +152,6 @@ namespace osu.Game.Rulesets.Objects foreach (var nested in nestedHitObjects) nested.StartTime += offset; - - DifficultyControlPoint.Time = time.NewValue; - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; } } @@ -178,6 +163,38 @@ namespace osu.Game.Rulesets.Objects HitWindows?.SetDifficulty(difficulty.OverallDifficulty); } + /// + /// Applies legacy information to this HitObject. + /// This method gets called at the end of before applying defaults. + /// + /// The control points. + /// The difficulty settings to use. + /// The cancellation token. + public void ApplyLegacyInfo(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) + { + var legacyInfo = controlPointInfo as LegacyControlPointInfo; + + DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; +#pragma warning disable 618 + if (difficultyControlPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyControlPoint) +#pragma warning restore 618 + LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier; + + ApplyLegacyInfoToSelf(controlPointInfo, difficulty); + + // This is done here after ApplyLegacyInfoToSelf as we may require custom defaults to be applied to have an accurate end time. + SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; + + foreach (var hitSampleInfo in Samples) + { + sampleControlPoint.ApplyTo(hitSampleInfo); + } + } + + protected virtual void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) + { + } + protected virtual void CreateNestedHitObjects(CancellationToken cancellationToken) { } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index bd2713a7d1..11e1f0beae 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,10 +9,11 @@ using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset, IHasSliderVelocity { /// /// Scoring distance with a speed-adjusted beat length of 1 second. @@ -40,17 +41,27 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Velocity = 1; + public double SliderVelocity { get; set; } = 1; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; } + protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) + { + base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); + + DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; + SliderVelocity = difficultyControlPoint.SliderVelocity; + } + public double LegacyLastTickOffset => 36; } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs b/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs new file mode 100644 index 0000000000..a7195dab4b --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.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. + +namespace osu.Game.Rulesets.Objects.Types; + +/// +/// A HitObject that has a slider velocity multiplier. +/// +public interface IHasSliderVelocity +{ + /// + /// The slider velocity multiplier. + /// + double SliderVelocity { get; set; } +} From ea1e6e9798b7773bdf130febf4026edbd1caac52 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 12:12:46 +0200 Subject: [PATCH 030/128] Add LegacyContext --- .../Legacy/DistanceObjectPatternGenerator.cs | 5 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 13 +-- .../Beatmaps/TaikoBeatmapConverter.cs | 5 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 16 +++- osu.Game/Beatmaps/Legacy/LegacyContext.cs | 32 +++++++ osu.Game/Context/IContext.cs | 10 +++ osu.Game/Context/IHasContext.cs | 88 +++++++++++++++++++ osu.Game/Rulesets/Objects/HitObject.cs | 15 +--- 8 files changed, 155 insertions(+), 29 deletions(-) create mode 100644 osu.Game/Beatmaps/Legacy/LegacyContext.cs create mode 100644 osu.Game/Context/IContext.cs create mode 100644 osu.Game/Context/IHasContext.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 9e031c2b4d..b3a68269f7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy @@ -51,8 +52,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); double beatLength; - if (hitObject.LegacyBpmMultiplier.HasValue) - beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value; + if (hitObject.HasContext()) + beatLength = timingPoint.BeatLength * hitObject.GetContext().BpmMultiplier; else if (hitObject is IHasSliderVelocity hasSliderVelocity) beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6952033aec..4010218f6e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -138,11 +138,6 @@ namespace osu.Game.Rulesets.Osu.Objects public double SliderVelocity { get; set; } = 1; - /// - /// Whether to generate ticks on this . - /// - public bool GenerateTicks = true; - [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } @@ -162,9 +157,10 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity; + bool generateTicks = !HasContext() || GetContext().GenerateTicks; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) @@ -172,12 +168,7 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; -#pragma warning disable 618 - var legacyDifficultyPoint = difficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; -#pragma warning restore 618 - SliderVelocity = difficultyControlPoint.SliderVelocity; - GenerateTicks = legacyDifficultyPoint?.GenerateTicks ?? true; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 362ddccaf1..ef73ffd517 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -16,6 +16,7 @@ using JetBrains.Annotations; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Taiko.Beatmaps { @@ -179,8 +180,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); double beatLength; - if (obj.LegacyBpmMultiplier.HasValue) - beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value; + if (obj.HasContext()) + beatLength = timingPoint.BeatLength * obj.GetContext().BpmMultiplier; else if (obj is IHasSliderVelocity hasSliderVelocity) beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index c17eea0e85..6f948e4d23 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats @@ -86,11 +87,24 @@ namespace osu.Game.Beatmaps.Formats foreach (var hitObject in this.beatmap.HitObjects) { - hitObject.ApplyLegacyInfo(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); + applyLegacyInfoToHitObject(hitObject); hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); } } + private void applyLegacyInfoToHitObject(HitObject hitObject) + { + var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo; + + DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(hitObject.StartTime) : DifficultyControlPoint.DEFAULT; +#pragma warning disable 618 + if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) +#pragma warning restore 618 + hitObject.SetContext(new LegacyContext(legacyDifficultyControlPoint.BpmMultiplier, legacyDifficultyControlPoint.GenerateTicks)); + + hitObject.ApplyLegacyInfo(beatmap.ControlPointInfo, beatmap.Difficulty); + } + /// /// Some `BeatmapInfo` members have default values that differ from the default values used by stable. /// In addition, legacy beatmaps will sometimes not contain some configuration keys, in which case diff --git a/osu.Game/Beatmaps/Legacy/LegacyContext.cs b/osu.Game/Beatmaps/Legacy/LegacyContext.cs new file mode 100644 index 0000000000..eeb02bdcb7 --- /dev/null +++ b/osu.Game/Beatmaps/Legacy/LegacyContext.cs @@ -0,0 +1,32 @@ +// 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.Context; + +namespace osu.Game.Beatmaps.Legacy; + +public class LegacyContext : IContext +{ + public LegacyContext(double bpmMultiplier, bool generateTicks) + { + BpmMultiplier = bpmMultiplier; + GenerateTicks = generateTicks; + } + + /// + /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. + /// DO NOT USE THIS UNLESS 100% SURE. + /// + public double BpmMultiplier { get; } + + /// + /// Whether or not slider ticks should be generated at this control point. + /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). + /// + public bool GenerateTicks { get; } + + public IContext Copy() + { + return new LegacyContext(BpmMultiplier, GenerateTicks); + } +} diff --git a/osu.Game/Context/IContext.cs b/osu.Game/Context/IContext.cs new file mode 100644 index 0000000000..f70cdea9f8 --- /dev/null +++ b/osu.Game/Context/IContext.cs @@ -0,0 +1,10 @@ +namespace osu.Game.Context; + +public interface IContext +{ + /// + /// Makes a deep copy of this context. + /// + /// The deep copy of this context. + public IContext Copy(); +} diff --git a/osu.Game/Context/IHasContext.cs b/osu.Game/Context/IHasContext.cs new file mode 100644 index 0000000000..bee81ea555 --- /dev/null +++ b/osu.Game/Context/IHasContext.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace osu.Game.Context +{ + public abstract class ContextContainer + { + /// + /// The contexts of this container. + /// The objects always have the type of their key. + /// + private readonly Dictionary contexts; + + protected ContextContainer() + { + contexts = new Dictionary(); + } + + /// + /// Checks whether this object has the context with type T. + /// + /// The type to check the context of. + /// Whether the context object with type T exists in this object. + public bool HasContext() where T : IContext + { + return contexts.ContainsKey(typeof(T)); + } + + /// + /// Gets the context with type T. + /// + /// The type to get the context of. + /// If the context does not exist in this hit object. + /// The context object with type T. + public T GetContext() where T : IContext + { + return (T)contexts[typeof(T)]; + } + + /// + /// Tries to get the context with type T. + /// + /// The found context with type T. + /// The type to get the context of. + /// Whether the context exists in this object. + public bool TryGetContext(out T context) where T : IContext + { + if (contexts.TryGetValue(typeof(T), out var context2)) + { + context = (T)context2; + return true; + } + + context = default!; + return false; + } + + /// + /// Sets the context object of type T. + /// + /// The context type to set. + /// The context object to store in this object. + public void SetContext(T context) where T : IContext + { + contexts[typeof(T)] = context; + } + + /// + /// Removes the context of type T from this object. + /// + /// The type to remove the context of. + /// Whether a context was removed. + public bool RemoveContext() where T : IContext + { + return RemoveContext(typeof(T)); + } + + /// + /// Removes the context of type T from this object. + /// + /// The type to remove the context of. + /// Whether a context was removed. + public bool RemoveContext(Type t) + { + return contexts.Remove(t); + } + } +} diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index aea564a4b9..fb7a5739b4 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; +using osu.Game.Context; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Objects /// HitObjects may contain more properties for which you should be checking through the IHas* types. /// /// - public class HitObject + public class HitObject : ContextContainer { /// /// A small adjustment to the start time of control points to account for rounding/precision errors. @@ -81,12 +82,6 @@ namespace osu.Game.Rulesets.Objects public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; - /// - /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. - /// DO NOT USE THIS UNLESS 100% SURE. - /// - public double? LegacyBpmMultiplier { get; private set; } - /// /// Whether this is in Kiai time. /// @@ -174,12 +169,6 @@ namespace osu.Game.Rulesets.Objects { var legacyInfo = controlPointInfo as LegacyControlPointInfo; - DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; -#pragma warning disable 618 - if (difficultyControlPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyControlPoint) -#pragma warning restore 618 - LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier; - ApplyLegacyInfoToSelf(controlPointInfo, difficulty); // This is done here after ApplyLegacyInfoToSelf as we may require custom defaults to be applied to have an accurate end time. From 891b87a5ff97e9eaeb7a0de15472217e97b2540c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 12:52:21 +0200 Subject: [PATCH 031/128] remove ApplyLegacyInfo method --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ----- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 36 ++++++++++++++++--- osu.Game/Rulesets/Objects/HitObject.cs | 26 -------------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 8 ----- 4 files changed, 32 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4010218f6e..a90d60cd1b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -163,14 +163,6 @@ namespace osu.Game.Rulesets.Osu.Objects TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } - protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) - { - base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); - - DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; - SliderVelocity = difficultyControlPoint.SliderVelocity; - } - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 6f948e4d23..dad23df282 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -17,6 +17,7 @@ using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps.Formats { @@ -27,6 +28,11 @@ namespace osu.Game.Beatmaps.Formats /// public const int EARLY_VERSION_TIMING_OFFSET = 24; + /// + /// A small adjustment to the start time of control points to account for rounding/precision errors. + /// + private const double control_point_leniency = 1; + internal static RulesetStore RulesetStore; private Beatmap beatmap; @@ -87,12 +93,11 @@ namespace osu.Game.Beatmaps.Formats foreach (var hitObject in this.beatmap.HitObjects) { - applyLegacyInfoToHitObject(hitObject); - hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); + applyLegacyInfoAndDefaults(hitObject); } } - private void applyLegacyInfoToHitObject(HitObject hitObject) + private void applyLegacyInfoAndDefaults(HitObject hitObject) { var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo; @@ -102,7 +107,30 @@ namespace osu.Game.Beatmaps.Formats #pragma warning restore 618 hitObject.SetContext(new LegacyContext(legacyDifficultyControlPoint.BpmMultiplier, legacyDifficultyControlPoint.GenerateTicks)); - hitObject.ApplyLegacyInfo(beatmap.ControlPointInfo, beatmap.Difficulty); + if (hitObject is IHasSliderVelocity hasSliderVelocity) + hasSliderVelocity.SliderVelocity = difficultyControlPoint.SliderVelocity; + + hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; + + foreach (var hitSampleInfo in hitObject.Samples) + { + sampleControlPoint.ApplyTo(hitSampleInfo); + } + + if (hitObject is not IHasRepeats hasRepeats) return; + + for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + { + double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; + sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(time) : SampleControlPoint.DEFAULT; + + foreach (var hitSampleInfo in hasRepeats.NodeSamples[i]) + { + sampleControlPoint.ApplyTo(hitSampleInfo); + } + } } /// diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index fb7a5739b4..2298a7ef12 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -158,32 +158,6 @@ namespace osu.Game.Rulesets.Objects HitWindows?.SetDifficulty(difficulty.OverallDifficulty); } - /// - /// Applies legacy information to this HitObject. - /// This method gets called at the end of before applying defaults. - /// - /// The control points. - /// The difficulty settings to use. - /// The cancellation token. - public void ApplyLegacyInfo(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) - { - var legacyInfo = controlPointInfo as LegacyControlPointInfo; - - ApplyLegacyInfoToSelf(controlPointInfo, difficulty); - - // This is done here after ApplyLegacyInfoToSelf as we may require custom defaults to be applied to have an accurate end time. - SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; - - foreach (var hitSampleInfo in Samples) - { - sampleControlPoint.ApplyTo(hitSampleInfo); - } - } - - protected virtual void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) - { - } - protected virtual void CreateNestedHitObjects(CancellationToken cancellationToken) { } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 11e1f0beae..47aabf1599 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -54,14 +54,6 @@ namespace osu.Game.Rulesets.Objects.Legacy Velocity = scoringDistance / timingPoint.BeatLength; } - protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) - { - base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); - - DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; - SliderVelocity = difficultyControlPoint.SliderVelocity; - } - public double LegacyLastTickOffset => 36; } } From bf1951fc38200c8372df7ada3ce19bc8c4a9443a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 12:54:45 +0200 Subject: [PATCH 032/128] Fix incorrect xmldoc --- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index c454439c5c..6cd4d74a31 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK) { Default = DEFAULT_BANK }; /// - /// The speed multiplier at this control point. + /// The default sample bank at this control point. /// public string SampleBank { @@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps.ControlPoints } /// - /// The default sample bank at this control point. + /// The default sample volume at this control point. /// public readonly BindableInt SampleVolumeBindable = new BindableInt(100) { From 97910d6be6b96bcb56d77d8489f41f80cd58f1b4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 13:06:37 +0200 Subject: [PATCH 033/128] remove unused directives --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - osu.Game/Rulesets/Objects/HitObject.cs | 2 -- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 1 - 3 files changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index a90d60cd1b..73ac184c57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -15,7 +15,6 @@ using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 2298a7ef12..0502610eab 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -16,8 +16,6 @@ using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps.Legacy; using osu.Game.Context; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 47aabf1599..f602cbac09 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,6 @@ using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Objects.Legacy { From c37875bee824e8c00bbc0d07273dbd98cbef5930 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 15:53:36 +0200 Subject: [PATCH 034/128] remove hitobject SampleControlPoint usage from LegacyBeatmapEncoder --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 072e442dea..04786cc9fb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); - writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}")); + writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints?.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); @@ -268,7 +268,12 @@ namespace osu.Game.Beatmaps.Formats { foreach (var hitObject in hitObjects) { - yield return hitObject.SampleControlPoint; + if (hitObject.Samples.Count > 0) + { + int volume = hitObject.Samples.Max(o => o.Volume); + int customIndex = hitObject.Samples.OfType().Max(o => o.CustomSampleBank); + yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex }; + } foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects)) yield return nested; From a6346171575e76ba0bf1b043040bbd0db8188b4e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 16:01:06 +0200 Subject: [PATCH 035/128] Fix file header notice --- osu.Game/Context/{IHasContext.cs => ContextContainer.cs} | 5 ++++- osu.Game/Context/IContext.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) rename osu.Game/Context/{IHasContext.cs => ContextContainer.cs} (94%) diff --git a/osu.Game/Context/IHasContext.cs b/osu.Game/Context/ContextContainer.cs similarity index 94% rename from osu.Game/Context/IHasContext.cs rename to osu.Game/Context/ContextContainer.cs index bee81ea555..7ff0280bcf 100644 --- a/osu.Game/Context/IHasContext.cs +++ b/osu.Game/Context/ContextContainer.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace osu.Game.Context diff --git a/osu.Game/Context/IContext.cs b/osu.Game/Context/IContext.cs index f70cdea9f8..61b1b11f43 100644 --- a/osu.Game/Context/IContext.cs +++ b/osu.Game/Context/IContext.cs @@ -1,4 +1,7 @@ -namespace osu.Game.Context; +// 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.Context; public interface IContext { From ebe1d852f53a80f3775f0ff602e95e8a74f3ca65 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 16:01:43 +0200 Subject: [PATCH 036/128] remove other usages of hitobject SampleControlPoint --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 6 +++--- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 8 +------- osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs | 1 - osu.Game/Rulesets/UI/Playfield.cs | 4 ++-- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 5b59a81f91..a2ae1764dd 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Edit.Checks yield break; // Samples that allow themselves to be overridden by control points have a volume of 0. - int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); + int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume); double samplePlayTime = sampledHitObject.GetEndTime(); EdgeType edgeType = getEdgeAtTime(hitObject, samplePlayTime); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index f810f51027..91dd7754d0 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -74,9 +74,9 @@ namespace osu.Game.Rulesets.Edit /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. protected void BeginPlacement(bool commitStart = false) { - var nearestSampleControlPoint = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.SampleControlPoint?.DeepClone() as SampleControlPoint; - - HitObject.SampleControlPoint = nearestSampleControlPoint ?? new SampleControlPoint(); + // Take the hitnormal sample of the last hit object + var lastHitNormal = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); + HitObject.Samples.Add(lastHitNormal); placementHandler.BeginPlacement(HitObject); if (commitStart) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f6c3452e48..79fc778287 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -357,13 +357,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (samples.Length <= 0) return; - if (HitObject.SampleControlPoint == null) - { - throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." - + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - } - - Samples.Samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + Samples.Samples = samples.Cast().ToArray(); } private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples(); diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index e1c03e49e3..d4510a4519 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -52,7 +52,6 @@ namespace osu.Game.Rulesets.UI return; var samples = nextObject.Samples - .Select(s => nextObject.SampleControlPoint.ApplyTo(s)) .Cast() .ToArray(); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index b1c3b78e67..6016a53918 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -293,10 +293,10 @@ namespace osu.Game.Rulesets.UI { // prepare sample pools ahead of time so we're not initialising at runtime. foreach (var sample in hitObject.Samples) - prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + prepareSamplePool(sample); foreach (var sample in hitObject.AuxiliarySamples) - prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + prepareSamplePool(sample); foreach (var nestedObject in hitObject.NestedHitObjects) preloadSamples(nestedObject); From 065464d90cae0c7ea887be51b7ca171011f70560 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 18:12:53 +0200 Subject: [PATCH 037/128] Fixed DifficultyPointPiece --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 ++- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 9 ++- .../Objects/Types/IHasSliderVelocity.cs | 4 + .../Timeline/DifficultyPointPiece.cs | 23 ++++-- .../Timeline/HitObjectPointPiece.cs | 77 +++++++++---------- .../Components/Timeline/SamplePointPiece.cs | 5 +- .../Timeline/TimelineHitObjectBlueprint.cs | 45 ++++------- 7 files changed, 90 insertions(+), 82 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 73ac184c57..efb98ba888 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -135,7 +136,13 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool OnlyJudgeNestedObjects = true; - public double SliderVelocity { get; set; } = 1; + public BindableDouble SliderVelocityBindable = new BindableDouble(1); + + public double SliderVelocity + { + get => SliderVelocityBindable.Value; + set => SliderVelocityBindable.Value = value; + } [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index f602cbac09..94d21a06ed 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -40,7 +41,13 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Velocity = 1; - public double SliderVelocity { get; set; } = 1; + public BindableDouble SliderVelocityBindable = new BindableDouble(1); + + public double SliderVelocity + { + get => SliderVelocityBindable.Value; + set => SliderVelocityBindable.Value = value; + } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { diff --git a/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs b/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs index a7195dab4b..c0ac5036ee 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs @@ -1,6 +1,8 @@ // 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.Bindables; + namespace osu.Game.Rulesets.Objects.Types; /// @@ -12,4 +14,6 @@ public interface IHasSliderVelocity /// The slider velocity multiplier. /// double SliderVelocity { get; set; } + + BindableNumber SliderVelocityBindable { get; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index d3cdd461ea..4741b75641 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -13,12 +13,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Timing; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -29,13 +31,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly BindableNumber speedMultiplier; public DifficultyPointPiece(HitObject hitObject) - : base(hitObject.DifficultyControlPoint) { HitObject = hitObject; - speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy(); + speedMultiplier = (hitObject as IHasSliderVelocity)?.SliderVelocityBindable.GetBoundCopy(); } + protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; + protected override void LoadComplete() { base.LoadComplete(); @@ -78,7 +81,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Spacing = new Vector2(0, 15), Children = new Drawable[] { - sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput("Velocity", new DifficultyControlPoint().SliderVelocityBindable) + sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput("Velocity", new BindableDouble(1) + { + Precision = 0.01, + MinValue = 0.1, + MaxValue = 10 + }) { KeyboardStep = 0.1f }, @@ -94,11 +102,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. - var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - var relevantControlPoints = relevantObjects.Select(h => h.DifficultyControlPoint).ToArray(); + var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).Where(o => o is IHasSliderVelocity).ToArray(); // even if there are multiple objects selected, we can still display a value if they all have the same value. - var selectedPointBindable = relevantControlPoints.Select(point => point.SliderVelocity).Distinct().Count() == 1 ? relevantControlPoints.First().SliderVelocityBindable : null; + var selectedPointBindable = relevantObjects.Select(point => ((IHasSliderVelocity)point).SliderVelocity).Distinct().Count() == 1 ? ((IHasSliderVelocity)relevantObjects.First()).SliderVelocityBindable : null; if (selectedPointBindable != null) { @@ -117,7 +124,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in relevantObjects) { - h.DifficultyControlPoint.SliderVelocity = val.NewValue.Value; + ((IHasSliderVelocity)h).SliderVelocity = val.NewValue.Value; beatmap.Update(h); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs index 5b0a5729c8..bd699c2e88 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -7,59 +7,54 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Compose.Components.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline; + +public partial class HitObjectPointPiece : CircularContainer { - public partial class HitObjectPointPiece : CircularContainer + protected OsuSpriteText Label { get; private set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - private readonly ControlPoint point; + AutoSizeAxes = Axes.Both; - protected OsuSpriteText Label { get; private set; } + Color4 colour = GetRepresentingColour(colours); - protected HitObjectPointPiece(ControlPoint point) + InternalChildren = new Drawable[] { - this.point = point; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.Both; - - Color4 colour = point.GetRepresentingColour(colours); - - InternalChildren = new Drawable[] + new Container { - new Container + AutoSizeAxes = Axes.X, + Height = 16, + Masking = true, + CornerRadius = 8, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] { - AutoSizeAxes = Axes.X, - Height = 16, - Masking = true, - CornerRadius = 8, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new Drawable[] + new Box { - new Box - { - Colour = colour, - RelativeSizeAxes = Axes.Both, - }, - Label = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Padding = new MarginPadding(5), - Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), - Colour = colours.B5, - } + Colour = colour, + RelativeSizeAxes = Axes.Both, + }, + Label = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5), + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + Colour = colours.B5, } - }, - }; - } + } + }, + }; + + protected virtual Color4 GetRepresentingColour(OsuColour colours) + { + return colours.Yellow; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 314137a565..50278bffc0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -13,10 +13,12 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -28,13 +30,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly BindableNumber volume; public SamplePointPiece(HitObject hitObject) - : base(hitObject.SampleControlPoint) { HitObject = hitObject; volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy(); bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy(); } + protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink; + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 4e5087c004..de659cddb8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -102,6 +102,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, } }, + sampleOverrideDisplay = new SamplePointPiece(Item) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopCentre + }, }); if (item is IHasDuration) @@ -111,6 +116,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled = e => OnDragHandled?.Invoke(e) }); } + + if (item is IHasSliderVelocity) + { + AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomCentre + } + ); + } } protected override void LoadComplete() @@ -208,36 +223,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (Item is IHasRepeats repeats) updateRepeats(repeats); } - - if (!ReferenceEquals(difficultyControlPoint, Item.DifficultyControlPoint)) - { - difficultyControlPoint = Item.DifficultyControlPoint; - difficultyOverrideDisplay?.Expire(); - - if (Item.DifficultyControlPoint != null && Item is IHasDistance) - { - AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item) - { - Anchor = Anchor.TopLeft, - Origin = Anchor.BottomCentre - }); - } - } - - if (!ReferenceEquals(sampleControlPoint, Item.SampleControlPoint)) - { - sampleControlPoint = Item.SampleControlPoint; - sampleOverrideDisplay?.Expire(); - - if (Item.SampleControlPoint != null) - { - AddInternal(sampleOverrideDisplay = new SamplePointPiece(Item) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopCentre - }); - } - } } private void updateRepeats(IHasRepeats repeats) From c23a7b014eb7a6a4b984bafc85459044cf29323e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 18:17:07 +0200 Subject: [PATCH 038/128] add missing } --- .../Edit/Compose/Components/Timeline/HitObjectPointPiece.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs index bd699c2e88..f7854705a4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -52,6 +52,7 @@ public partial class HitObjectPointPiece : CircularContainer } }, }; + } protected virtual Color4 GetRepresentingColour(OsuColour colours) { From 66eda40cdf1911c2c4139c00fa9ebc8478058168 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 18:22:22 +0200 Subject: [PATCH 039/128] fix implementations of IHasSliderVelocity --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index efb98ba888..e8eb197186 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool OnlyJudgeNestedObjects = true; - public BindableDouble SliderVelocityBindable = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); public double SliderVelocity { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 94d21a06ed..7ddd372dc9 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Velocity = 1; - public BindableDouble SliderVelocityBindable = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); public double SliderVelocity { From 755ad25dbe22987fd1cd5e0c851adf1f99698640 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 18:27:20 +0200 Subject: [PATCH 040/128] clean code --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index de659cddb8..1c36bec53e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, } }, - sampleOverrideDisplay = new SamplePointPiece(Item) + new SamplePointPiece(Item) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopCentre @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (item is IHasSliderVelocity) { - AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item) + AddInternal(new DifficultyPointPiece(Item) { Anchor = Anchor.TopLeft, Origin = Anchor.BottomCentre @@ -202,12 +202,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline colouredComponents.Colour = OsuColour.ForegroundTextColourFor(averageColour); } - private SamplePointPiece? sampleOverrideDisplay; - private DifficultyPointPiece? difficultyOverrideDisplay; - - private DifficultyControlPoint difficultyControlPoint = null!; - private SampleControlPoint sampleControlPoint = null!; - protected override void Update() { base.Update(); From e4b64bdc3e0b3e3074c76a8286e986acfdeedb57 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 19:06:29 +0200 Subject: [PATCH 041/128] clean up code stuff --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 1 - osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - .../Components/Timeline/TimelineHitObjectBlueprint.cs | 9 ++++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index b3a68269f7..2427812ecd 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Utils; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index e8eb197186..247cf94f59 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; -using System.Text.Json.Serialization; using System.Threading; using Newtonsoft.Json; using osu.Framework.Bindables; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 1c36bec53e..c2106e0598 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -120,11 +120,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (item is IHasSliderVelocity) { AddInternal(new DifficultyPointPiece(Item) - { - Anchor = Anchor.TopLeft, - Origin = Anchor.BottomCentre - } - ); + { + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomCentre + }); } } From e27c4dfa101fe132f76614e32815c6a34ba455bc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 11:46:05 +0200 Subject: [PATCH 042/128] Invoke ApplyDefaultsToSelf --- osu.Game/Rulesets/Objects/HitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 0502610eab..7702635057 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Objects /// The cancellation token. public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) { + ApplyDefaultsToSelf(controlPointInfo, difficulty); + nestedHitObjects.Clear(); CreateNestedHitObjects(cancellationToken); From 6c7094868163c7aeda18116d352739ba8309d57c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 13:10:57 +0200 Subject: [PATCH 043/128] Remove IContext & add IHasGenerateTicks --- .../Legacy/DistanceObjectPatternGenerator.cs | 4 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +- .../Beatmaps/TaikoBeatmapConverter.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +- osu.Game/Beatmaps/Legacy/LegacyContext.cs | 32 ------- osu.Game/Context/ContextContainer.cs | 91 ------------------- osu.Game/Context/IContext.cs | 13 --- osu.Game/Rulesets/Objects/HitObject.cs | 9 +- .../Objects/Types/IHasGenerateTicks.cs | 17 ++++ 9 files changed, 37 insertions(+), 147 deletions(-) delete mode 100644 osu.Game/Beatmaps/Legacy/LegacyContext.cs delete mode 100644 osu.Game/Context/ContextContainer.cs delete mode 100644 osu.Game/Context/IContext.cs create mode 100644 osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 2427812ecd..20f39deed7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); double beatLength; - if (hitObject.HasContext()) - beatLength = timingPoint.BeatLength * hitObject.GetContext().BpmMultiplier; + if (hitObject.LegacyBpmMultiplier.HasValue) + beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value; else if (hitObject is IHasSliderVelocity hasSliderVelocity) beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 247cf94f59..dd75a86f96 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -15,14 +15,13 @@ using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity + public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; @@ -143,6 +142,8 @@ namespace osu.Game.Rulesets.Osu.Objects set => SliderVelocityBindable.Value = value; } + public bool GenerateTicks { get; set; } + [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } @@ -162,10 +163,9 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity; - bool generateTicks = !HasContext() || GetContext().GenerateTicks; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index ef73ffd517..2cf1d7a6ab 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -180,8 +180,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); double beatLength; - if (obj.HasContext()) - beatLength = timingPoint.BeatLength * obj.GetContext().BpmMultiplier; + if (obj.LegacyBpmMultiplier.HasValue) + beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value; else if (obj is IHasSliderVelocity hasSliderVelocity) beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index dad23df282..9a1935f929 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -105,7 +105,11 @@ namespace osu.Game.Beatmaps.Formats #pragma warning disable 618 if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) #pragma warning restore 618 - hitObject.SetContext(new LegacyContext(legacyDifficultyControlPoint.BpmMultiplier, legacyDifficultyControlPoint.GenerateTicks)); + { + hitObject.LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier; + if (hitObject is IHasGenerateTicks hasGenerateTicks) + hasGenerateTicks.GenerateTicks = legacyDifficultyControlPoint.GenerateTicks; + } if (hitObject is IHasSliderVelocity hasSliderVelocity) hasSliderVelocity.SliderVelocity = difficultyControlPoint.SliderVelocity; diff --git a/osu.Game/Beatmaps/Legacy/LegacyContext.cs b/osu.Game/Beatmaps/Legacy/LegacyContext.cs deleted file mode 100644 index eeb02bdcb7..0000000000 --- a/osu.Game/Beatmaps/Legacy/LegacyContext.cs +++ /dev/null @@ -1,32 +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.Game.Context; - -namespace osu.Game.Beatmaps.Legacy; - -public class LegacyContext : IContext -{ - public LegacyContext(double bpmMultiplier, bool generateTicks) - { - BpmMultiplier = bpmMultiplier; - GenerateTicks = generateTicks; - } - - /// - /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. - /// DO NOT USE THIS UNLESS 100% SURE. - /// - public double BpmMultiplier { get; } - - /// - /// Whether or not slider ticks should be generated at this control point. - /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). - /// - public bool GenerateTicks { get; } - - public IContext Copy() - { - return new LegacyContext(BpmMultiplier, GenerateTicks); - } -} diff --git a/osu.Game/Context/ContextContainer.cs b/osu.Game/Context/ContextContainer.cs deleted file mode 100644 index 7ff0280bcf..0000000000 --- a/osu.Game/Context/ContextContainer.cs +++ /dev/null @@ -1,91 +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; - -namespace osu.Game.Context -{ - public abstract class ContextContainer - { - /// - /// The contexts of this container. - /// The objects always have the type of their key. - /// - private readonly Dictionary contexts; - - protected ContextContainer() - { - contexts = new Dictionary(); - } - - /// - /// Checks whether this object has the context with type T. - /// - /// The type to check the context of. - /// Whether the context object with type T exists in this object. - public bool HasContext() where T : IContext - { - return contexts.ContainsKey(typeof(T)); - } - - /// - /// Gets the context with type T. - /// - /// The type to get the context of. - /// If the context does not exist in this hit object. - /// The context object with type T. - public T GetContext() where T : IContext - { - return (T)contexts[typeof(T)]; - } - - /// - /// Tries to get the context with type T. - /// - /// The found context with type T. - /// The type to get the context of. - /// Whether the context exists in this object. - public bool TryGetContext(out T context) where T : IContext - { - if (contexts.TryGetValue(typeof(T), out var context2)) - { - context = (T)context2; - return true; - } - - context = default!; - return false; - } - - /// - /// Sets the context object of type T. - /// - /// The context type to set. - /// The context object to store in this object. - public void SetContext(T context) where T : IContext - { - contexts[typeof(T)] = context; - } - - /// - /// Removes the context of type T from this object. - /// - /// The type to remove the context of. - /// Whether a context was removed. - public bool RemoveContext() where T : IContext - { - return RemoveContext(typeof(T)); - } - - /// - /// Removes the context of type T from this object. - /// - /// The type to remove the context of. - /// Whether a context was removed. - public bool RemoveContext(Type t) - { - return contexts.Remove(t); - } - } -} diff --git a/osu.Game/Context/IContext.cs b/osu.Game/Context/IContext.cs deleted file mode 100644 index 61b1b11f43..0000000000 --- a/osu.Game/Context/IContext.cs +++ /dev/null @@ -1,13 +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.Context; - -public interface IContext -{ - /// - /// Makes a deep copy of this context. - /// - /// The deep copy of this context. - public IContext Copy(); -} diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 7702635057..aaeffa49fc 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -16,7 +16,6 @@ using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Context; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Objects /// HitObjects may contain more properties for which you should be checking through the IHas* types. /// /// - public class HitObject : ContextContainer + public class HitObject { /// /// A small adjustment to the start time of control points to account for rounding/precision errors. @@ -80,6 +79,12 @@ namespace osu.Game.Rulesets.Objects public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; + /// + /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. + /// DO NOT USE THIS UNLESS 100% SURE. + /// + public double? LegacyBpmMultiplier { get; set; } + /// /// Whether this is in Kiai time. /// diff --git a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs new file mode 100644 index 0000000000..5de7d348c5 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs @@ -0,0 +1,17 @@ +// 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.Rulesets.Objects.Types +{ + /// + /// A type of which may or may not generate ticks. + /// + public interface IHasGenerateTicks + { + /// + /// Whether or not slider ticks should be generated at this control point. + /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). + /// + public bool GenerateTicks { get; set; } + } +} From 39d9f0c3f5d689506a0971d22d9e40fab01ecee4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 13:22:13 +0200 Subject: [PATCH 044/128] removing using --- .../Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 20f39deed7..91b7be6e8f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Legacy; using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy From 87ca0f5335ebd07abe29c3958bf2b5cd88f947ef Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 13:45:58 +0200 Subject: [PATCH 045/128] Update SamplePointPiece.cs --- .../Components/Timeline/SamplePointPiece.cs | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 50278bffc0..b02cfb505e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; @@ -26,14 +26,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public readonly HitObject HitObject; - private readonly Bindable bank; - private readonly BindableNumber volume; + private readonly BindableList samplesBindable; public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; - volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy(); - bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy(); + samplesBindable = hitObject.SamplesBindable.GetBoundCopy(); } protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink; @@ -41,8 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - volume.BindValueChanged(_ => updateText()); - bank.BindValueChanged(_ => updateText(), true); + samplesBindable.BindCollectionChanged((_, _) => updateText(), true); } protected override bool OnClick(ClickEvent e) @@ -53,7 +50,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { - Label.Text = $"{bank.Value} {volume.Value}"; + Label.Text = $"{GetBankValue(samplesBindable)} {GetVolumeValue(samplesBindable)}"; + } + + public static string? GetBankValue(IEnumerable samples) + { + return samples.FirstOrDefault()?.Bank; + } + + public static int GetVolumeValue(ICollection samples) + { + return samples.Count == 0 ? 0 : samples.Max(o => o.Volume); } public Popover GetPopover() => new SampleEditPopover(HitObject); @@ -92,7 +99,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Label = "Bank Name", }, - volume = new IndeterminateSliderWithTextBoxInput("Volume", new SampleControlPoint().SampleVolumeBindable) + volume = new IndeterminateSliderWithTextBoxInput("Volume", new BindableInt(100) + { + MinValue = 0, + MaxValue = 100, + }) } } }; @@ -103,14 +114,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray(); + var relevantSamples = relevantObjects.Select(h => h.Samples).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. - string? commonBank = getCommonBank(relevantControlPoints); + string? commonBank = getCommonBank(relevantSamples); if (!string.IsNullOrEmpty(commonBank)) bank.Current.Value = commonBank; - int? commonVolume = getCommonVolume(relevantControlPoints); + int? commonVolume = getCommonVolume(relevantSamples); if (commonVolume != null) volume.Current.Value = commonVolume.Value; @@ -120,9 +131,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateBankFor(relevantObjects, val.NewValue); updateBankPlaceholderText(relevantObjects); }); - // on commit, ensure that the value is correct by sourcing it from the objects' control points again. + // on commit, ensure that the value is correct by sourcing it from the objects' samples again. // this ensures that committing empty text causes a revert to the previous value. - bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantControlPoints); + bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantSamples); volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); } @@ -133,8 +144,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume)); } - private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; - private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? relevantControlPoints.First().SampleVolume : null; + private static string? getCommonBank(IList[] relevantSamples) => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; + private static int? getCommonVolume(IList[] relevantSamples) => relevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(relevantSamples.First()) : null; private void updateBankFor(IEnumerable objects, string? newBank) { @@ -145,7 +156,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in objects) { - h.SampleControlPoint.SampleBank = newBank; + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newBank: newBank); + } + beatmap.Update(h); } @@ -154,7 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateBankPlaceholderText(IEnumerable objects) { - string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray()); + string? commonBank = getCommonBank(objects.Select(h => h.Samples).ToArray()); bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; } @@ -167,7 +182,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in objects) { - h.SampleControlPoint.SampleVolume = newVolume.Value; + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newVolume: newVolume.Value); + } + beatmap.Update(h); } From d97daee96be2c10f90709cc30beceb1f369ae225 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 13:55:39 +0200 Subject: [PATCH 046/128] remove all non-test usage of SampleControlPoint --- .../Objects/Drawables/DrawableHoldNote.cs | 8 +------- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 10 ---------- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 1 - .../Objects/Drawables/DrawableSlider.cs | 10 ++-------- .../Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs | 7 ++++--- 7 files changed, 9 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 6e1c6cf80f..372ef1e164 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -350,13 +350,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { // Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being. - if (HitObject.SampleControlPoint == null) - { - throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." - + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - } - - slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + slidingSample.Samples = HitObject.CreateSlidingSamples().Cast().ToArray(); } public override void StopAllSamples() diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index e444287b73..8cf64a6a7e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -311,17 +311,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var splitControlPoints = controlPoints.Take(index + 1).ToList(); controlPoints.RemoveRange(0, index); - // Turn the control points which were split off into a new slider. - var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone(); - var difficultyPoint = (DifficultyControlPoint)HitObject.DifficultyControlPoint.DeepClone(); - var newSlider = new Slider { StartTime = HitObject.StartTime, Position = HitObject.Position + splitControlPoints[0].Position, NewCombo = HitObject.NewCombo, - SampleControlPoint = samplePoint, - DifficultyControlPoint = difficultyPoint, LegacyLastTickOffset = HitObject.LegacyLastTickOffset, Samples = HitObject.Samples.Select(s => s.With()).ToList(), RepeatCount = HitObject.RepeatCount, @@ -378,15 +372,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition); - var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone(); - samplePoint.Time = time; - editorBeatmap.Add(new HitCircle { StartTime = time, Position = position, NewCombo = i == 0 && HitObject.NewCombo, - SampleControlPoint = samplePoint, Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList() }); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 6d5280e528..2a6d6ce4c3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -362,7 +362,6 @@ namespace osu.Game.Rulesets.Osu.Edit StartTime = firstHitObject.StartTime, Position = firstHitObject.Position, NewCombo = firstHitObject.NewCombo, - SampleControlPoint = firstHitObject.SampleControlPoint, Samples = firstHitObject.Samples, }; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index a7b02596d5..664a8146e7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -133,14 +133,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being. - if (HitObject.SampleControlPoint == null) - { - throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." - + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - } - - Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); - slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + Samples.Samples = HitObject.TailSamples.Cast().ToArray(); + slidingSample.Samples = HitObject.CreateSlidingSamples().Cast().ToArray(); } public override void StopAllSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a5193f1b6e..0ceda1d4b0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + spinningSample.Samples = HitObject.CreateSpinningSamples().Cast().ToArray(); spinningSample.Frequency.Value = spinning_sample_initial_frequency; } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index ed6f8a9a6a..55924c19c9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects return new[] { - SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin") + referenceSample.With("spinnerspin") }; } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs b/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs index 4809791af8..92f2b74568 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Game.Audio; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; @@ -17,12 +18,12 @@ namespace osu.Game.Rulesets.Taiko.UI public void Play(HitType hitType) { - var hitObject = GetMostValidObject(); + var hitSample = GetMostValidObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - if (hitObject == null) + if (hitSample == null) return; - PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) }); + PlaySamples(new ISampleInfo[] { new HitSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL, hitSample.Bank, volume: hitSample.Volume) }); } public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead"); From c6fc1806595df9be3e3cb738fae20a0efaa8839d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 14:21:52 +0200 Subject: [PATCH 047/128] remove all test usages of SampleControlPoint --- .../Editor/TestSceneObjectMerging.cs | 3 +- .../Editor/TestSceneSliderSplitting.cs | 10 ++----- .../Editor/TestSceneSliderStreamConversion.cs | 3 +- .../Formats/LegacyBeatmapDecoderTest.cs | 6 ++-- .../Editing/TestSceneEditorClipboard.cs | 4 --- .../Visual/Editing/TestSceneEditorSaving.cs | 10 ------- ...estSceneHitObjectSamplePointAdjustments.cs | 29 ++++++++++++------- .../TestSceneGameplaySampleTriggerSource.cs | 6 ++-- osu.Game/Audio/HitSampleInfo.cs | 5 ++-- osu.Game/Rulesets/Objects/HitObject.cs | 1 - 10 files changed, 32 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index e7ac38c20e..b05c755bfd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -138,8 +138,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples) && mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples) - && mergedSlider.Samples.SequenceEqual(slider1.Samples) - && mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint); + && mergedSlider.Samples.SequenceEqual(slider1.Samples); }); AddAssert("slider end is at same completion for last slider", () => diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index 6cb77c7b92..a104433ea9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -181,10 +181,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { if (slider is null) return; - slider.SampleControlPoint.SampleBank = "soft"; - slider.SampleControlPoint.SampleVolume = 70; - sample = new HitSampleInfo("hitwhistle"); - slider.Samples.Add(sample); + sample = new HitSampleInfo("hitwhistle", "soft", volume: 70); + slider.Samples.Add(sample.With()); }); AddStep("select added slider", () => @@ -207,9 +205,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("sliders have hitsounds", hasHitsounds); bool hasHitsounds() => sample is not null && - EditorBeatmap.HitObjects.All(o => o.SampleControlPoint.SampleBank == "soft" && - o.SampleControlPoint.SampleVolume == 70 && - o.Samples.Contains(sample)); + EditorBeatmap.HitObjects.All(o => o.Samples.Contains(sample)); } private bool sliderCreatedFor(Slider s, double startTime, double endTime, params (Vector2 pos, PathType? pathType)[] expectedControlPoints) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs index 53465d43c9..a162d9a491 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -199,8 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Precision.AlmostEquals(circle.StartTime, time, 1) && Precision.AlmostEquals(circle.Position, position, 0.01f) && circle.NewCombo == startsNewCombo - && circle.Samples.SequenceEqual(slider.HeadCircle.Samples) - && circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint); + && circle.Samples.SequenceEqual(slider.HeadCircle.Samples); } private bool sliderRestored(Slider slider) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 518981980b..622a1837eb 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -480,7 +480,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); } - static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0]; } [Test] @@ -498,7 +498,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); } - static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0]; } [Test] @@ -518,7 +518,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume); } - static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0]; } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index d26bb6bb8a..3c98a83fa0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -95,10 +95,6 @@ namespace osu.Game.Tests.Visual.Editing var path = slider.Path; return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints); }); - - // see `HitObject.control_point_leniency`. - AddAssert("sample control point has correct time", () => Precision.AlmostEquals(slider.SampleControlPoint.Time, slider.GetEndTime(), 1)); - AddAssert("difficulty control point has correct time", () => slider.DifficultyControlPoint.Time == slider.StartTime); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index b396b382ff..64c48e74cf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -122,19 +122,9 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500); - // After placement these must be non-default as defaults are read-only. - AddAssert("Placed object has non-default control points", () => - !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) && - !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); - ReloadEditorToSameBeatmap(); AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500); - - // After placement these must be non-default as defaults are read-only. - AddAssert("Placed object still has non-default control points", () => - !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) && - !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index e8dcc6f19b..7403cad52f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -7,6 +7,7 @@ using System.Linq; using Humanizer; using NUnit.Framework; using osu.Framework.Testing; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; @@ -39,10 +40,9 @@ namespace osu.Game.Tests.Visual.Editing { StartTime = 0, Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2, - SampleControlPoint = new SampleControlPoint + Samples = new List { - SampleBank = "normal", - SampleVolume = 80 + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 80) } }); @@ -50,10 +50,9 @@ namespace osu.Game.Tests.Visual.Editing { StartTime = 500, Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2, - SampleControlPoint = new SampleControlPoint + Samples = new List { - SampleBank = "soft", - SampleVolume = 60 + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft", volume: 60) } }); }); @@ -96,7 +95,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("unify sample volume", () => { foreach (var h in EditorBeatmap.HitObjects) - h.SampleControlPoint.SampleVolume = 50; + { + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newVolume: 50); + } + } }); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); @@ -136,7 +140,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("unify sample bank", () => { foreach (var h in EditorBeatmap.HitObjects) - h.SampleControlPoint.SampleBank = "soft"; + { + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newBank: "soft"); + } + } }); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); @@ -248,7 +257,7 @@ namespace osu.Game.Tests.Visual.Editing private void hitObjectHasSampleVolume(int objectIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} has volume {volume}", () => { var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); - return h.SampleControlPoint.SampleVolume == volume; + return h.Samples.All(o => o.Volume == volume); }); private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () => @@ -265,7 +274,7 @@ namespace osu.Game.Tests.Visual.Editing private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () => { var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); - return h.SampleControlPoint.SampleBank == bank; + return h.Samples.All(o => o.Bank == bank); }); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index 31133f00d9..114c554d28 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -73,8 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay new HitCircle { StartTime = t += spacing, - Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, - SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft") }, }, new HitCircle { @@ -84,8 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay { StartTime = t += spacing, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }), - Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) }, - SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, "soft") }, }, }); diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index efa5562cb8..e9c06152cc 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Utils; namespace osu.Game.Audio @@ -32,7 +33,7 @@ namespace osu.Game.Audio /// /// The bank to load the sample from. /// - public readonly string? Bank; + public readonly string Bank; /// /// An optional suffix to provide priority lookup. Falls back to non-suffixed . @@ -47,7 +48,7 @@ namespace osu.Game.Audio public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 0) { Name = name; - Bank = bank; + Bank = bank ?? SampleControlPoint.DEFAULT_BANK; Suffix = suffix; Volume = volume; } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index aaeffa49fc..73ecc28404 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -76,7 +76,6 @@ namespace osu.Game.Rulesets.Objects /// public virtual IList AuxiliarySamples => ImmutableList.Empty; - public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; /// From 5accb05f45d81d3e7fc385cdb53053d9aa9ee2c6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 14:28:48 +0200 Subject: [PATCH 048/128] fix invalidoperation exception --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 04786cc9fb..a4783046c4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -271,7 +271,9 @@ namespace osu.Game.Beatmaps.Formats if (hitObject.Samples.Count > 0) { int volume = hitObject.Samples.Max(o => o.Volume); - int customIndex = hitObject.Samples.OfType().Max(o => o.CustomSampleBank); + int customIndex = hitObject.Samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo) + ? hitObject.Samples.OfType().Max(o => o.CustomSampleBank) + : 0; yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex }; } From 1b4f4372d5304553768c8b83bd6d460bad5acfc0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 14:32:12 +0200 Subject: [PATCH 049/128] fixed sample control point applying --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9a1935f929..6b9e21f916 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -118,10 +118,7 @@ namespace osu.Game.Beatmaps.Formats SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; - foreach (var hitSampleInfo in hitObject.Samples) - { - sampleControlPoint.ApplyTo(hitSampleInfo); - } + hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); if (hitObject is not IHasRepeats hasRepeats) return; @@ -130,10 +127,7 @@ namespace osu.Game.Beatmaps.Formats double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(time) : SampleControlPoint.DEFAULT; - foreach (var hitSampleInfo in hasRepeats.NodeSamples[i]) - { - sampleControlPoint.ApplyTo(hitSampleInfo); - } + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => sampleControlPoint.ApplyTo(o)).ToList(); } } From 9f8d7bccbab8e011ca69288476fae517a62b1c38 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 17:34:02 +0200 Subject: [PATCH 050/128] fix usings --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 1 - osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 1 - .../Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 8cf64a6a7e..6685507ee0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Audio; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2cf1d7a6ab..362ddccaf1 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -16,7 +16,6 @@ using JetBrains.Annotations; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Taiko.Beatmaps { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 7403cad52f..7a0418cfec 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -4,12 +4,12 @@ #nullable disable using System.Linq; +using System.Collections.Generic; using Humanizer; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets; From c44f71a7374f370831a72b3c13d0343368e6f86b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 17:55:38 +0200 Subject: [PATCH 051/128] remove all regular usage of DifficultyControlPoint --- .../Edit/Blueprints/Components/EditablePath.cs | 2 +- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 15 ++++++++++++--- .../Sliders/SliderPlacementBlueprint.cs | 8 +++----- .../Beatmaps/TaikoBeatmapConverter.cs | 4 +++- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 13 +++++++++++-- ...estSceneHitObjectDifficultyPointAdjustments.cs | 5 +---- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 ++++- .../Rulesets/Edit/DistancedHitObjectComposer.cs | 3 ++- osu.Game/Rulesets/Objects/HitObject.cs | 2 +- .../Timeline/TimelineHitObjectBlueprint.cs | 12 ++++-------- 10 files changed, 42 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 74d6565600..7a577f8a83 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void UpdateHitObjectFromPath(JuiceStream hitObject) { // The SV setting may need to be changed for the current path. - var svBindable = hitObject.DifficultyControlPoint.SliderVelocityBindable; + var svBindable = hitObject.SliderVelocityBindable; double svToVelocityFactor = hitObject.Velocity / svBindable.Value; double requiredVelocity = path.ComputeRequiredVelocity(); diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 96e2d5c4e5..f8af161ad5 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -16,7 +17,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class JuiceStream : CatchHitObject, IHasPathWithRepeats + public class JuiceStream : CatchHitObject, IHasPathWithRepeats, IHasSliderVelocity { /// /// Positional distance that results in a duration of one second, before any speed adjustments. @@ -27,6 +28,14 @@ namespace osu.Game.Rulesets.Catch.Objects public int RepeatCount { get; set; } + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + + public double SliderVelocity + { + get => SliderVelocityBindable.Value; + set => SliderVelocityBindable.Value = value; + } + [JsonIgnore] private double velocityFactor; @@ -34,10 +43,10 @@ namespace osu.Game.Rulesets.Catch.Objects private double tickDistanceFactor; [JsonIgnore] - public double Velocity => velocityFactor * DifficultyControlPoint.SliderVelocity; + public double Velocity => velocityFactor * SliderVelocity; [JsonIgnore] - public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity; + public double TickDistance => tickDistanceFactor * SliderVelocity; /// /// The length of one span of this . diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 77393efeb3..50514865e1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -83,11 +82,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case SliderPlacementState.Initial: BeginPlacement(); - var nearestDifficultyPoint = editorBeatmap.HitObjects - .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime)? - .DifficultyControlPoint?.DeepClone() as DifficultyControlPoint; + double? nearestSliderVelocity = (editorBeatmap.HitObjects + .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocity; - HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint(); + HitObject.SliderVelocity = nearestSliderVelocity ?? 1; HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); // Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation. diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 362ddccaf1..05fb314342 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -64,7 +64,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps foreach (HitObject hitObject in original.HitObjects) { - double nextScrollSpeed = hitObject.DifficultyControlPoint.SliderVelocity; + if (hitObject is not IHasSliderVelocity hasSliderVelocity) continue; + + double nextScrollSpeed = hasSliderVelocity.SliderVelocity; EffectControlPoint currentEffectPoint = converted.ControlPointInfo.EffectPointAt(hitObject.StartTime); if (!Precision.AlmostEquals(lastScrollSpeed, nextScrollSpeed, acceptableDifference: currentEffectPoint.ScrollSpeedBindable.Precision)) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 3325eda7cf..5613bb190a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Threading; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -15,7 +16,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Objects { - public class DrumRoll : TaikoStrongableHitObject, IHasPath + public class DrumRoll : TaikoStrongableHitObject, IHasPath, IHasSliderVelocity { /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. @@ -35,6 +36,14 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double Velocity { get; private set; } + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + + public double SliderVelocity + { + get => SliderVelocityBindable.Value; + set => SliderVelocityBindable.Value = value; + } + /// /// Numer of ticks per beat length. /// @@ -52,7 +61,7 @@ namespace osu.Game.Rulesets.Taiko.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; + double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; tickSpacing = timingPoint.BeatLength / TickRate; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index ab82678eb9..f34e286f50 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -61,10 +61,7 @@ namespace osu.Game.Tests.Visual.Editing new PathControlPoint(new Vector2(100, 0)) } }, - DifficultyControlPoint = new DifficultyControlPoint - { - SliderVelocity = 2 - } + SliderVelocity = 2 }); }); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index a4783046c4..a681429d02 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -249,7 +249,10 @@ namespace osu.Game.Beatmaps.Formats yield break; foreach (var hitObject in hitObjects) - yield return hitObject.DifficultyControlPoint; + { + if (hitObject is IHasSliderVelocity hasSliderVelocity) + yield return new DifficultyControlPoint { Time = hitObject.StartTime, SliderVelocity = hasSliderVelocity.SliderVelocity }; + } } void extractDifficultyControlPoints(IEnumerable hitObjects) diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs index aa47b4f424..5aa9e3c179 100644 --- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -23,6 +23,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.OSD; using osu.Game.Overlays.Settings.Sections; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.TernaryButtons; namespace osu.Game.Rulesets.Edit @@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Edit public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true) { - return (float)(100 * (useReferenceSliderVelocity ? referenceObject.DifficultyControlPoint.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1 + return (float)(100 * (useReferenceSliderVelocity && referenceObject is IHasSliderVelocity hasSliderVelocity ? hasSliderVelocity.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1 / BeatSnapProvider.BeatDivisor); } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 73ecc28404..4822ec3919 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Objects /// public virtual IList AuxiliarySamples => ImmutableList.Empty; - public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; + public DifficultyControlPoint DifficultyControlPoint { get; set; } = DifficultyControlPoint.DEFAULT; /// /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index c2106e0598..61a3931839 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -373,17 +372,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasRepeats repeatHitObject: double proposedDuration = time - hitObject.StartTime; - if (e.CurrentState.Keyboard.ShiftPressed) + if (e.CurrentState.Keyboard.ShiftPressed && hitObject is IHasSliderVelocity hasSliderVelocity) { - if (ReferenceEquals(hitObject.DifficultyControlPoint, DifficultyControlPoint.DEFAULT)) - hitObject.DifficultyControlPoint = new DifficultyControlPoint(); + double newVelocity = hasSliderVelocity.SliderVelocity * (repeatHitObject.Duration / proposedDuration); - double newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); - - if (Precision.AlmostEquals(newVelocity, hitObject.DifficultyControlPoint.SliderVelocity)) + if (Precision.AlmostEquals(newVelocity, hasSliderVelocity.SliderVelocity)) return; - hitObject.DifficultyControlPoint.SliderVelocity = newVelocity; + hasSliderVelocity.SliderVelocity = newVelocity; beatmap.Update(hitObject); } else From 354cd238742546522b78b74bd03238b49180551e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 18:17:02 +0200 Subject: [PATCH 052/128] removed all usage of hitobject's DifficultyControlPoint --- .../TestSceneJuiceStreamPlacementBlueprint.cs | 4 ++-- .../TestSceneJuiceStreamSelectionBlueprint.cs | 4 ++-- .../TestSceneObjectOrderedHitPolicy.cs | 2 +- .../TestSceneSliderFollowCircleInput.cs | 3 +-- .../TestSceneSliderInput.cs | 3 +-- .../TestSceneStartTimeOrderedHitPolicy.cs | 2 +- ...tSceneHitObjectComposerDistanceSnapping.cs | 22 ++++++------------- .../Editing/TestSceneEditorClipboard.cs | 1 - ...ceneHitObjectDifficultyPointAdjustments.cs | 8 +++---- osu.Game/Rulesets/Objects/HitObject.cs | 2 -- 10 files changed, 19 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs index 18d3d29bdc..2426f8c886 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1])); AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0])); AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1])); - AddAssert("default slider velocity", () => lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); + AddAssert("default slider velocity", () => lastObject.SliderVelocityBindable.IsDefault); } [Test] @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor addPlacementSteps(times, positions); addPathCheckStep(times, positions); - AddAssert("slider velocity changed", () => !lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); + AddAssert("slider velocity changed", () => !lastObject.SliderVelocityBindable.IsDefault); } [Test] diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index f25b66c360..beba5811fe 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -108,11 +108,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor double[] times = { 100, 300 }; float[] positions = { 200, 300 }; addBlueprintStep(times, positions); - AddAssert("default slider velocity", () => hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); + AddAssert("default slider velocity", () => hitObject.SliderVelocityBindable.IsDefault); addDragStartStep(times[1], positions[1]); AddMouseMoveStep(times[1], 400); - AddAssert("slider velocity changed", () => !hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); + AddAssert("slider velocity changed", () => !hitObject.SliderVelocityBindable.IsDefault); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 5d9316a21b..ee70441688 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -439,7 +439,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public TestSlider() { - DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f }; + SliderVelocity = 0.1f; DefaultsApplied += _ => { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs index a32f0a13b8..fc2e6d1f72 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -47,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider_start, Position = new Vector2(0, 0), - DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity }, + SliderVelocity = velocity, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 5f27cdc191..d83926ab9b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -8,7 +8,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -350,7 +349,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider_start, Position = new Vector2(0, 0), - DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f }, + SliderVelocity = 0.1f, Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs index 29e6fc4301..f4257a9ee7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs @@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public TestSlider() { - DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f }; + SliderVelocity = 0.1f; DefaultsApplied += _ => { diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index f556f6e2fe..6399507aa0 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; @@ -74,12 +75,9 @@ namespace osu.Game.Tests.Editing [TestCase(2)] public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier) { - assertSnapDistance(100, new HitObject + assertSnapDistance(100, new Slider { - DifficultyControlPoint = new DifficultyControlPoint - { - SliderVelocity = multiplier - } + SliderVelocity = multiplier }, false); } @@ -87,12 +85,9 @@ namespace osu.Game.Tests.Editing [TestCase(2)] public void TestSpeedMultiplierDoesChangeDistanceSnap(float multiplier) { - assertSnapDistance(100 * multiplier, new HitObject + assertSnapDistance(100 * multiplier, new Slider { - DifficultyControlPoint = new DifficultyControlPoint - { - SliderVelocity = multiplier - } + SliderVelocity = multiplier }, true); } @@ -114,12 +109,9 @@ namespace osu.Game.Tests.Editing const float base_distance = 100; const float slider_velocity = 1.2f; - var referenceObject = new HitObject + var referenceObject = new Slider { - DifficultyControlPoint = new DifficultyControlPoint - { - SliderVelocity = slider_velocity - } + SliderVelocity = slider_velocity }; assertSnapDistance(base_distance * slider_velocity, referenceObject, true); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 3c98a83fa0..c4c05278b5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index f34e286f50..3b998b4219 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -8,10 +8,10 @@ using Humanizer; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; @@ -97,8 +97,8 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("unify slider velocity", () => { - foreach (var h in EditorBeatmap.HitObjects) - h.DifficultyControlPoint.SliderVelocity = 1.5; + foreach (var h in EditorBeatmap.HitObjects.OfType()) + h.SliderVelocity = 1.5; }); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Editing private void hitObjectHasVelocity(int objectIndex, double velocity) => AddAssert($"{objectIndex.ToOrdinalWords()} has velocity {velocity}", () => { var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); - return h.DifficultyControlPoint.SliderVelocity == velocity; + return h is IHasSliderVelocity hasSliderVelocity && hasSliderVelocity.SliderVelocity == velocity; }); } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 4822ec3919..095ec1ff1b 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -76,8 +76,6 @@ namespace osu.Game.Rulesets.Objects /// public virtual IList AuxiliarySamples => ImmutableList.Empty; - public DifficultyControlPoint DifficultyControlPoint { get; set; } = DifficultyControlPoint.DEFAULT; - /// /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. /// DO NOT USE THIS UNLESS 100% SURE. From f7c84030ac3c6cd1156f496540169840ed73421d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 28 Apr 2023 17:22:34 +0200 Subject: [PATCH 053/128] remove bank default value --- osu.Game/Audio/HitSampleInfo.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index e9c06152cc..efa5562cb8 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Utils; namespace osu.Game.Audio @@ -33,7 +32,7 @@ namespace osu.Game.Audio /// /// The bank to load the sample from. /// - public readonly string Bank; + public readonly string? Bank; /// /// An optional suffix to provide priority lookup. Falls back to non-suffixed . @@ -48,7 +47,7 @@ namespace osu.Game.Audio public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 0) { Name = name; - Bank = bank ?? SampleControlPoint.DEFAULT_BANK; + Bank = bank; Suffix = suffix; Volume = volume; } From ffcc8e91b242a22a17701566ac284a62d8f057c1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 29 Apr 2023 23:51:49 +0200 Subject: [PATCH 054/128] fix legacy parser incorrectly assigning sample info for sliders --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 68ca6bc506..ba5de6c14b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } if (split.Length > 10) - readCustomSampleBanks(split[10], bankInfo); + readCustomSampleBanks(split[10], bankInfo, true); // One node for each repeat + the start and end nodes int nodes = repeatCount + 2; @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Objects.Legacy return result; } - private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) + private void readCustomSampleBanks(string str, SampleBankInfo bankInfo, bool banksOnly = false) { if (string.IsNullOrEmpty(str)) return; @@ -202,6 +202,8 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.BankForNormal = stringBank; bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; + if (banksOnly) return; + if (split.Length > 2) bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]); From 92efd04f3138b62d49458fb296021e7101fb3105 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 29 Apr 2023 23:52:24 +0200 Subject: [PATCH 055/128] fix sample of drumroll ticks being bankless --- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 5 ++++- .../Objects/TaikoStrongableHitObject.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 11 +++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 5613bb190a..c1a78f46b2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,9 +3,11 @@ #nullable disable +using System.Linq; using osu.Game.Rulesets.Objects.Types; using System.Threading; using osu.Framework.Bindables; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -90,7 +92,8 @@ namespace osu.Game.Rulesets.Taiko.Objects FirstTick = first, TickSpacing = tickSpacing, StartTime = t, - IsStrong = IsStrong + IsStrong = IsStrong, + Samples = Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToList() }); first = false; diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs index d4d59d5d44..0043f231d2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Objects if (IsStrongBindable.Value != strongSamples.Any()) { if (IsStrongBindable.Value) - Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + Samples.Add(GetSampleInfo(HitSampleInfo.HIT_FINISH)); else { foreach (var sample in strongSamples) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 095ec1ff1b..774ff9dc1d 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -204,6 +204,17 @@ namespace osu.Game.Rulesets.Objects return slidingSamples; } + + /// + /// Create a SampleInfo based on the sample settings of the hit normal sample in . + /// + /// The name of the sample. + /// A populated . + protected HitSampleInfo GetSampleInfo(string sampleName) + { + var hitnormalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + return hitnormalSample == null ? new HitSampleInfo(sampleName) : hitnormalSample.With(newName: sampleName); + } } public static class HitObjectExtensions From a6e780a1b9c5740e74c17d9c9f098a9e30e4871f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 29 Apr 2023 23:52:30 +0200 Subject: [PATCH 056/128] Update CheckMutedObjectsTest.cs --- .../Editing/Checks/CheckMutedObjectsTest.cs | 104 +----------------- 1 file changed, 6 insertions(+), 98 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs index 1e1c214c30..5a3ef619d1 100644 --- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -37,45 +37,6 @@ namespace osu.Game.Tests.Editing.Checks cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted }); } - [Test] - public void TestNormalControlPointVolume() - { - var hitCircle = new HitCircle - { - StartTime = 0, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertOk(new List { hitCircle }); - } - - [Test] - public void TestLowControlPointVolume() - { - var hitCircle = new HitCircle - { - StartTime = 1000, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertLowVolume(new List { hitCircle }); - } - - [Test] - public void TestMutedControlPointVolume() - { - var hitCircle = new HitCircle - { - StartTime = 2000, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertMuted(new List { hitCircle }); - } - [Test] public void TestNormalSampleVolume() { @@ -122,7 +83,7 @@ namespace osu.Game.Tests.Editing.Checks var sliderHead = new SliderHeadCircle { StartTime = 0, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } }; sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -135,7 +96,7 @@ namespace osu.Game.Tests.Editing.Checks var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) { - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } }; slider.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -155,13 +116,13 @@ namespace osu.Game.Tests.Editing.Checks var sliderTick = new SliderTick { StartTime = 250, - Samples = new List { new HitSampleInfo("slidertick") } + Samples = new List { new HitSampleInfo("slidertick", volume: volume_regular) } }; sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) { - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail. + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } // Applies to the tail. }; slider.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -174,14 +135,14 @@ namespace osu.Game.Tests.Editing.Checks var sliderHead = new SliderHeadCircle { StartTime = 0, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } }; sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); var sliderTick = new SliderTick { StartTime = 250, - Samples = new List { new HitSampleInfo("slidertick") } + Samples = new List { new HitSampleInfo("slidertick", volume: volume_regular) } }; sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -194,59 +155,6 @@ namespace osu.Game.Tests.Editing.Checks assertMutedPassive(new List { slider }); } - [Test] - public void TestMutedControlPointVolumeSliderHead() - { - var sliderHead = new SliderHeadCircle - { - StartTime = 2000, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); - - var sliderTick = new SliderTick - { - StartTime = 2250, - Samples = new List { new HitSampleInfo("slidertick") } - }; - sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); - - var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500) - { - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } - }; - slider.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertMuted(new List { slider }); - } - - [Test] - public void TestMutedControlPointVolumeSliderTail() - { - var sliderHead = new SliderHeadCircle - { - StartTime = 0, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); - - var sliderTick = new SliderTick - { - StartTime = 250, - Samples = new List { new HitSampleInfo("slidertick") } - }; - sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); - - // Ends after the 5% control point. - var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) - { - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - slider.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertMutedPassive(new List { slider }); - } - private void assertOk(List hitObjects) { Assert.That(check.Run(getContext(hitObjects)), Is.Empty); From 83111223e0d9ef4ea85c16746a6d79c0e31b6bd2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 01:08:52 +0200 Subject: [PATCH 057/128] fix null sample --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 91dd7754d0..96128c6981 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -76,7 +76,8 @@ namespace osu.Game.Rulesets.Edit { // Take the hitnormal sample of the last hit object var lastHitNormal = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - HitObject.Samples.Add(lastHitNormal); + if (lastHitNormal != null) + HitObject.Samples[0] = lastHitNormal; placementHandler.BeginPlacement(HitObject); if (commitStart) From b39a9d816e1881bd40e471aa738fe8d455828871 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Apr 2023 16:05:45 +0900 Subject: [PATCH 058/128] Add basic structural requirements for cursor ripples --- .../Configuration/OsuRulesetConfigManager.cs | 2 ++ .../Legacy/OsuLegacySkinTransformer.cs | 5 +++++ .../UI/Cursor/CursorRippleVisualiser.cs | 21 +++++++++++++++++++ .../UI/Cursor/OsuCursorContainer.cs | 1 + .../UI/OsuSettingsSubsection.cs | 5 +++++ .../Localisation/RulesetSettingsStrings.cs | 5 +++++ 6 files changed, 39 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index b8ad61e6dd..2056a50eda 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Configuration SetDefault(OsuRulesetSetting.SnakingInSliders, true); SetDefault(OsuRulesetSetting.SnakingOutSliders, true); SetDefault(OsuRulesetSetting.ShowCursorTrail, true); + SetDefault(OsuRulesetSetting.ShowCursorRipples, false); SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); } } @@ -31,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Configuration SnakingInSliders, SnakingOutSliders, ShowCursorTrail, + ShowCursorRipples, PlayfieldBorderStyle, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 620540b8ef..279835747f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -100,6 +100,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; + case OsuSkinComponents.CursorRipple: + // TODO: resize texture to 0.5?? but that might break skins.. + if (GetTexture("cursor-ripple") != null) + return this.GetAnimation("cursor-ripple", false, false); + case OsuSkinComponents.CursorParticles: if (GetTexture("star2") != null) return new LegacyCursorParticles(); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs new file mode 100644 index 0000000000..ef0cae7de8 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -0,0 +1,21 @@ +// 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.Graphics.Containers; +using osu.Game.Rulesets.Osu.Configuration; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public partial class CursorRippleVisualiser : CompositeDrawable + { + private readonly Bindable showRipples = new Bindable(true); + + [BackgroundDependencyLoader(true)] + private void load(OsuRulesetConfigManager rulesetConfig) + { + rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showRipples); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 5d7648b073..2b541bd345 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Children = new[] { cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling), + new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipples), confineMode: ConfineMode.NoScaling), new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling), } }; diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 64c4e7eef6..0e410dbf57 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -43,6 +43,11 @@ namespace osu.Game.Rulesets.Osu.UI LabelText = RulesetSettingsStrings.CursorTrail, Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) }, + new SettingsCheckbox + { + LabelText = RulesetSettingsStrings.CursorRipples, + Current = config.GetBindable(OsuRulesetSetting.ShowCursorRipples) + }, new SettingsEnumDropdown { LabelText = RulesetSettingsStrings.PlayfieldBorderStyle, diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs index 1b0df6ecf6..52e6a5eaac 100644 --- a/osu.Game/Localisation/RulesetSettingsStrings.cs +++ b/osu.Game/Localisation/RulesetSettingsStrings.cs @@ -29,6 +29,11 @@ namespace osu.Game.Localisation /// public static LocalisableString CursorTrail => new TranslatableString(getKey(@"cursor_trail"), @"Cursor trail"); + /// + /// "Cursor ripples" + /// + public static LocalisableString CursorRipples => new TranslatableString(getKey(@"cursor_ripples"), @"Cursor ripples"); + /// /// "Playfield border style" /// From a4ae9e409bf1cb5e9c2f614e2967dc3bf82210b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Apr 2023 16:06:08 +0900 Subject: [PATCH 059/128] Implement ripples (legacy and default) --- .../TestSceneGameplayCursor.cs | 13 +++++ osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 + .../Legacy/OsuLegacySkinTransformer.cs | 2 + .../UI/Cursor/CursorRippleVisualiser.cs | 53 +++++++++++++++++-- .../UI/Cursor/OsuCursorContainer.cs | 2 +- 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 907422858e..c84a6ab70f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -18,6 +19,7 @@ using osu.Framework.Testing.Input; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; @@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable background; + private readonly Bindable ripples = new Bindable(); + public TestSceneGameplayCursor() { var ruleset = new OsuRuleset(); @@ -57,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Tests }); }); + AddToggleStep("ripples", v => ripples.Value = v); + AddSliderStep("circle size", 0f, 10f, 0f, val => { config.SetValue(OsuSetting.AutoCursorSize, true); @@ -67,6 +73,13 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("test cursor container", () => loadContent(false)); } + [BackgroundDependencyLoader] + private void load() + { + var rulesetConfig = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull(); + rulesetConfig.BindWith(OsuRulesetSetting.ShowCursorRipples, ripples); + } + [TestCase(1, 1)] [TestCase(5, 1)] [TestCase(10, 1)] diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 8fdf3821fa..52fdfea95f 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Osu Cursor, CursorTrail, CursorParticles, + CursorRipple, SliderScorePoint, ReverseArrow, HitCircleText, diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 279835747f..bf817eda29 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (GetTexture("cursor-ripple") != null) return this.GetAnimation("cursor-ripple", false, false); + return null; + case OsuSkinComponents.CursorParticles: if (GetTexture("star2") != null) return new LegacyCursorParticles(); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index ef0cae7de8..401525efcd 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -3,19 +3,66 @@ 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.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.UI.Cursor { - public partial class CursorRippleVisualiser : CompositeDrawable + public partial class CursorRippleVisualiser : CompositeDrawable, IKeyBindingHandler { private readonly Bindable showRipples = new Bindable(true); [BackgroundDependencyLoader(true)] - private void load(OsuRulesetConfigManager rulesetConfig) + private void load(OsuRulesetConfigManager? rulesetConfig) { - rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showRipples); + rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorRipples, showRipples); + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (showRipples.Value) + { + var ripple = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipple), _ => new DefaultCursorRipple()) + { + Blending = BlendingParameters.Additive, + Position = e.MousePosition + }; + + AddInternal(ripple); + + ripple.ScaleTo(0.05f) + .ScaleTo(0.5f, 700, Easing.Out) + .FadeTo(0.2f) + .FadeOut(700) + .Expire(); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + + public partial class DefaultCursorRipple : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new RingPiece(3) + { + Size = new Vector2(512), + } + }; + } } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 2b541bd345..35fb8e67d8 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Children = new[] { cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling), - new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipples), confineMode: ConfineMode.NoScaling), + new CursorRippleVisualiser(), new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling), } }; From c994adfc22a3c7a25985988bd2b9f48ac47c3b58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Apr 2023 22:00:17 +0900 Subject: [PATCH 060/128] Add pooling support for ripples --- .../UI/Cursor/CursorRippleVisualiser.cs | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index 401525efcd..e13e0d5dfb 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Configuration; @@ -18,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { private readonly Bindable showRipples = new Bindable(true); + private readonly DrawablePool ripplePool = new DrawablePool(20); + [BackgroundDependencyLoader(true)] private void load(OsuRulesetConfigManager? rulesetConfig) { @@ -27,21 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public bool OnPressed(KeyBindingPressEvent e) { if (showRipples.Value) - { - var ripple = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipple), _ => new DefaultCursorRipple()) - { - Blending = BlendingParameters.Additive, - Position = e.MousePosition - }; - - AddInternal(ripple); - - ripple.ScaleTo(0.05f) - .ScaleTo(0.5f, 700, Easing.Out) - .FadeTo(0.2f) - .FadeOut(700) - .Expire(); - } + AddInternal(ripplePool.Get()); return false; } @@ -50,6 +39,37 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { } + private partial class CursorRipple : PoolableDrawable + { + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipple), _ => new DefaultCursorRipple()) + { + Blending = BlendingParameters.Additive, + } + }; + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + ClearTransforms(true); + + this.ScaleTo(0.05f) + .ScaleTo(0.5f, 700, Easing.Out) + .FadeTo(0.2f) + .FadeOut(700) + .Expire(); + } + } + public partial class DefaultCursorRipple : CompositeDrawable { [BackgroundDependencyLoader] From 6a62949fcd36e4ee62fdfb8de528901784733cf6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Apr 2023 10:38:21 +0900 Subject: [PATCH 061/128] Fix positioning and rewinding support for ripples --- .../UI/Cursor/CursorRippleVisualiser.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index e13e0d5dfb..27e9d1d347 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -21,6 +21,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly DrawablePool ripplePool = new DrawablePool(20); + public CursorRippleVisualiser() + { + RelativeSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader(true)] private void load(OsuRulesetConfigManager? rulesetConfig) { @@ -30,7 +35,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public bool OnPressed(KeyBindingPressEvent e) { if (showRipples.Value) - AddInternal(ripplePool.Get()); + { + AddInternal(ripplePool.Get(r => + { + r.Position = e.MousePosition; + })); + } return false; } @@ -66,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor .ScaleTo(0.5f, 700, Easing.Out) .FadeTo(0.2f) .FadeOut(700) - .Expire(); + .Expire(true); } } @@ -75,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [BackgroundDependencyLoader] private void load() { + AutoSizeAxes = Axes.Both; + InternalChildren = new Drawable[] { new RingPiece(3) From 72b472a75604a97d1f15ee211550bf28e2b4204c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Apr 2023 11:19:03 +0900 Subject: [PATCH 062/128] Change default scaling and add note about legacy `cursor-ripple` scale --- .../Legacy/OsuLegacySkinTransformer.cs | 19 +++++++++++++++++-- .../UI/Cursor/CursorRippleVisualiser.cs | 8 ++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index bf817eda29..f049aa088f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -101,9 +101,24 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.CursorRipple: - // TODO: resize texture to 0.5?? but that might break skins.. if (GetTexture("cursor-ripple") != null) - return this.GetAnimation("cursor-ripple", false, false); + { + var ripple = this.GetAnimation("cursor-ripple", false, false); + + // In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible. + // If anyone complains about these not being applied, this can be uncommented. + // + // But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size, + // so we might be okay. + // + // if (ripple != null) + // { + // ripple.Scale = new Vector2(0.5f); + // ripple.Alpha = 0.2f; + // } + + return ripple; + } return null; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index 27e9d1d347..465b708b5a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -73,9 +73,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor ClearTransforms(true); this.ScaleTo(0.05f) - .ScaleTo(0.5f, 700, Easing.Out) - .FadeTo(0.2f) - .FadeOut(700) + .ScaleTo(1, 700, Easing.Out) + .FadeOutFromOne(700) .Expire(true); } } @@ -91,7 +90,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { new RingPiece(3) { - Size = new Vector2(512), + Size = new Vector2(256), + Alpha = 0.2f, } }; } From 1eb2e35dffb3215b3c58987fa6b244710babc49a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 16:03:58 +0200 Subject: [PATCH 063/128] fix ticks not being generated by default --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index dd75a86f96..98e536de38 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Objects set => SliderVelocityBindable.Value = value; } - public bool GenerateTicks { get; set; } + public bool GenerateTicks { get; set; } = true; [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } From e7a478ce9c7e8dc20be47173233f0700fd6d88e9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 16:04:03 +0200 Subject: [PATCH 064/128] Update convert-samples-expected-conversion.json --- .../Testing/Beatmaps/convert-samples-expected-conversion.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json index 6f1d45ad8c..4d298bb671 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -10,7 +10,7 @@ ["Gameplay/soft-hitnormal"], ["Gameplay/drum-hitnormal"] ], - "Samples": ["Gameplay/-hitnormal"] + "Samples": ["Gameplay/normal-hitnormal"] }, { "StartTime": 1875.0, "EndTime": 2750.0, @@ -19,7 +19,7 @@ ["Gameplay/soft-hitnormal"], ["Gameplay/drum-hitnormal"] ], - "Samples": ["Gameplay/-hitnormal"] + "Samples": ["Gameplay/normal-hitnormal"] }] }, { "StartTime": 3750.0, From 79f3cfec91e58bc67cb3a1df1d099fc08e991938 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 16:43:26 +0200 Subject: [PATCH 065/128] fix 0 velocity juicestream --- .../Edit/Blueprints/Components/EditablePath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 7a577f8a83..75ee0546dd 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components // // The value is clamped here by the bindable min and max values. // In case the required velocity is too large, the path is not preserved. - svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor); + svBindable.Value = requiredVelocity == 0 ? 1 : Math.Ceiling(requiredVelocity / svToVelocityFactor); path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); From d35355970f2aa40dbddb54dee6eacac225ad5f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 30 Apr 2023 17:23:45 +0200 Subject: [PATCH 066/128] Add test case covering failure scenario --- .../TestSceneModSelectOverlay.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 5ccaebd721..f99fe1d8d4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -14,6 +14,7 @@ using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; @@ -67,6 +68,19 @@ namespace osu.Game.Tests.Visual.UserInterface } } }); + r.Add(new ModPreset + { + Name = "Half Time 0.5x", + Description = "Very slow", + Ruleset = r.Find(OsuRuleset.SHORT_NAME), + Mods = new[] + { + new OsuModHalfTime + { + SpeedChange = { Value = 0.5 } + } + } + }); }); }); } @@ -566,6 +580,28 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("5 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 5); } + [Test] + public void TestModMultiplierUpdates() + { + createScreen(); + + AddStep("select mod preset with half time", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single(preset => preset.Preset.Value.Name == "Half Time 0.5x")); + InputManager.Click(MouseButton.Left); + }); + AddAssert("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.5)); + + // this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation, + // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. + AddStep("force collection", GC.Collect); + + AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick()); + AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() + .ChildrenOfType>().Single().TriggerClick()); + AddUntilStep("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.7)); + } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => modSelectOverlay.ChildrenOfType().Any() && modSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); From 2e3daf0a541e14fb518292fabcc2ae8dde319e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 30 Apr 2023 17:24:07 +0200 Subject: [PATCH 067/128] Fix leak of `ModSettingChangeTracker` instances The `SelectedMods.BindValueChanged()` callback in `ModSelectOverlay` can in some instances run recursively. This is most heavily leaned on in scenarios where `SelectedMods` is updated by an external component. In such cases, the mod select overlay needs to replace the mod instances received externally with mod instances which it owns, so that the changes made on the overlay can propagate outwards. This in particular means that prior to this commit, it was possible to encounter the following scenario: modSettingChangeTracker?.Dispose(); updateFromExternalSelection(); // mutates SelectedMods to perform the replacement // therefore causing a recursive call modSettingChangeTracker?.Dispose(); // inner call continues modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); // outer call continues modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); This leaks one `modSettingChangeTracker` instance from the inner call, which is never disposed. To avoid this, move the disposal to the same side of the recursion that the creation happens on, changing the call pattern to: updateFromExternalSelection(); // mutates SelectedMods to perform the replacement // therefore causing a recursive call modSettingChangeTracker?.Dispose(); // inner call continues modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); modSettingChangeTracker?.Dispose(); // outer call continues modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); which, while slightly wasteful, does not cause any leaks. The solution is definitely suboptimal, but addressing this properly would entail a major rewrite of the mod instance management in the mods overlay, which is probably not the wisest move to make right now. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d611fd3c0b..46d3620c9c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -244,12 +244,12 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(val => { - modSettingChangeTracker?.Dispose(); - updateMultiplier(); updateFromExternalSelection(); updateCustomisation(); + modSettingChangeTracker?.Dispose(); + if (AllowCustomisation) { modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); From 139a1d7e6d6ec8f5d5c13de881f4ddfdd2542bc4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 17:46:47 +0200 Subject: [PATCH 068/128] fix legacy encoder writing sample info while not writing node samples --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index a681429d02..c5a8cc89be 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -476,16 +476,16 @@ namespace osu.Game.Beatmaps.Formats if (curveData != null) { - for (int i = 0; i < curveData.NodeSamples.Count; i++) + for (int i = 0; i < curveData.SpanCount() + 1; i++) { - writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + writer.Write(FormattableString.Invariant($"{(i < curveData.NodeSamples.Count ? (int)toLegacyHitSoundType(curveData.NodeSamples[i]) : 0)}")); + writer.Write(i != curveData.SpanCount() ? "|" : ","); } - for (int i = 0; i < curveData.NodeSamples.Count; i++) + for (int i = 0; i < curveData.SpanCount() + 1; i++) { - writer.Write(getSampleBank(curveData.NodeSamples[i], true)); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + writer.Write(i < curveData.NodeSamples.Count ? getSampleBank(curveData.NodeSamples[i], true) : "0:0"); + writer.Write(i != curveData.SpanCount() ? "|" : ","); } } } From 4a0ff046ae302ceb2b2eb379055ec119eb240b4a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 19:20:42 +0200 Subject: [PATCH 069/128] pass new hitobject properties through beatmap converters --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs | 4 +++- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs | 6 +++++- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 7774a7da09..2c8ef9eae0 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps var xPositionData = obj as IHasXPosition; var yPositionData = obj as IHasYPosition; var comboData = obj as IHasCombo; + var sliderVelocityData = obj as IHasSliderVelocity; switch (obj) { @@ -41,7 +42,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0, - LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y + LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y, + SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1 }.Yield(); case IHasDuration endTime: diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index e9518895be..d03ee81f0d 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { var positionData = original as IHasPosition; var comboData = original as IHasCombo; + var sliderVelocityData = original as IHasSliderVelocity; + var generateTicksData = original as IHasGenerateTicks; switch (original) { @@ -47,7 +49,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset, // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // this results in more (or less) ticks being generated in Date: Sun, 30 Apr 2023 19:32:24 +0200 Subject: [PATCH 070/128] add min and max value to SliderVelocity --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 7 ++++++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 7 ++++++- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index f8af161ad5..169e99c90c 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -28,7 +28,12 @@ namespace osu.Game.Rulesets.Catch.Objects public int RepeatCount { get; set; } - public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1) + { + Precision = 0.01, + MinValue = 0.1, + MaxValue = 10 + }; public double SliderVelocity { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 98e536de38..4189f8ba1e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -134,7 +134,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool OnlyJudgeNestedObjects = true; - public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1) + { + Precision = 0.01, + MinValue = 0.1, + MaxValue = 10 + }; public double SliderVelocity { diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index c1a78f46b2..b4a12fd314 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -38,7 +38,12 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double Velocity { get; private set; } - public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1) + { + Precision = 0.01, + MinValue = 0.1, + MaxValue = 10 + }; public double SliderVelocity { From 2a947571546bd883716e7a182a52100a68210548 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 00:49:01 +0200 Subject: [PATCH 071/128] Make sure the first object you place has bank and volume --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 96128c6981..bdcb334738 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Edit HitObject = hitObject; // adding the default hit sample should be the case regardless of the ruleset. - HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK, volume: 100)); RelativeSizeAxes = Axes.Both; From b8ae508639421a89dbfe0fadb1b433bdaa46d05f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 May 2023 13:09:00 +0900 Subject: [PATCH 072/128] Fix incorrect starting scale for ripples --- osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index 465b708b5a..663c1f54fc 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor ClearTransforms(true); - this.ScaleTo(0.05f) + this.ScaleTo(0.1f) .ScaleTo(1, 700, Easing.Out) .FadeOutFromOne(700) .Expire(true); From 5cbfefbcb4d6177a4012527d28d53d56c13ffab0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 May 2023 13:18:04 +0900 Subject: [PATCH 073/128] Adjust metrics of default ripple to match stable default better --- osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index 663c1f54fc..cee5574f06 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -90,8 +91,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { new RingPiece(3) { - Size = new Vector2(256), - Alpha = 0.2f, + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2), + Alpha = 0.1f, } }; } From 0a70734331f2ba319c74c9b909ddcca3f338a6a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 May 2023 14:40:48 +0900 Subject: [PATCH 074/128] Adjust ripple size with cursor scale (including CS) --- .../UI/Cursor/CursorRippleVisualiser.cs | 18 +++++++++++------- .../UI/Cursor/OsuCursorContainer.cs | 5 ++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index cee5574f06..076d97d06a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor RelativeSizeAxes = Axes.Both; } + public Vector2 CursorScale { get; set; } = Vector2.One; + [BackgroundDependencyLoader(true)] private void load(OsuRulesetConfigManager? rulesetConfig) { @@ -40,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor AddInternal(ripplePool.Get(r => { r.Position = e.MousePosition; + r.Scale = CursorScale; })); } @@ -52,18 +55,17 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private partial class CursorRipple : PoolableDrawable { + private Drawable ripple = null!; + [BackgroundDependencyLoader] private void load() { AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; - InternalChildren = new Drawable[] + InternalChild = ripple = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipple), _ => new DefaultCursorRipple()) { - new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipple), _ => new DefaultCursorRipple()) - { - Blending = BlendingParameters.Additive, - } + Blending = BlendingParameters.Additive, }; } @@ -73,8 +75,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor ClearTransforms(true); - this.ScaleTo(0.1f) - .ScaleTo(1, 700, Easing.Out) + ripple.ScaleTo(0.1f) + .ScaleTo(1, 700, Easing.Out); + + this .FadeOutFromOne(700) .Expire(true); } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 35fb8e67d8..bf1ff872dd 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private Bindable userCursorScale; private Bindable autoCursorScale; + private readonly CursorRippleVisualiser rippleVisualiser; + public OsuCursorContainer() { InternalChild = fadeContainer = new Container @@ -48,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Children = new[] { cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling), - new CursorRippleVisualiser(), + rippleVisualiser = new CursorRippleVisualiser(), new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling), } }; @@ -83,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor var newScale = new Vector2(e.NewValue); ActiveCursor.Scale = newScale; + rippleVisualiser.CursorScale = newScale; cursorTrail.Scale = newScale; }, true); From ff29189e8951945c6ef701159cc7636a75032d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 May 2023 09:33:37 +0200 Subject: [PATCH 075/128] Add custom `cursor-ripple` to cover skinnability in test --- .../Resources/special-skin/cursor-ripple@2x.png | Bin 0 -> 2149 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/cursor-ripple@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/cursor-ripple@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/cursor-ripple@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..258162c4864ddc469ba21697770b2fc8e1297eb5 GIT binary patch literal 2149 zcmcgsX;4#V6us|}kS8dFF-#Ic5|_5rT2^f>iv$Bgv@GHZ1A?;2AVv@s6p|2B6e7xW zlxhVxtS~NA5Em3f6%wnCsC65ug_3cob&Xbu8uEHSrtPn`e>!7ka^Ac5o_o&u^4?@q zY;?p3hfxjyFhUU-76(A#Ed`7{UJ~=_Gq6~fC6~u4$>GtPQ{e0-z%ys3EC>aJhf-of7Z^qsf-otCubWW*_atCL+XG%{l}KK3qE zJJPQx;zpf&wKAq{&gx;c4|)bNUo~(mD5|PpyZGCvv!Ow4S#G-qHl-e(c&MYlBlW|i zt(K}s227|wKV7@3M&Kw8hQ>S7shd&{f=X2*z(Bu6#$MS$(MqiyF}RJ(ZDDev_IHcKYigqyWfn0mafQXJTG&9a-P zZ+ju;oVC05gOMGuHLv2Fd!j!x5Dnm|}@n!fi!HEkh|KDMDWe-yn9#<6jxP$&`zJdSWLeZrcAtD)9n?HxowT38fCiXBKhX zNTaMwKn(T~D?S%ZOFaLT+P>07WU6jubmo~!9E0V67oBR26PEa@8YfHiobTSJam)=<3+kH)j3YO6AWl_5pq4je%{>Id;MBdWWTQYP3+T7>3@7Dgwi+~={G(>p5BOWJ`rCSF zl>EZLb|b5G(9F7Ed)Zp6ny|Y^vuE%x`{8Fsb{QGBnf0HLakllD6B&zHd_iz*U>6Y& znOo~tC6A+%fRn=z4j)1%f_#m}YzH*oM+GvV4bH&G+AS1N zj3@-8K$&>Jai21TK?KvR_@KzzR0V#bM(d$o4wcb}jV@rKJ(;Bb} O@S_Ni4m&z6Mf(Thel05i literal 0 HcmV?d00001 From cef9f73d346cdafb9e4d5c7aa3f4a5a25f82ef1a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 12:31:27 +0200 Subject: [PATCH 076/128] dont assign custom sample bank and volume to hitobjects in non-mania gamemodes this makes it easier to edit hitsounds in the stable editor after export because the sample control point effects wont get overwritten by the properties of the hitobject --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index c5a8cc89be..28f3a8d951 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -520,6 +520,14 @@ namespace osu.Game.Beatmaps.Formats string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty; int volume = samples.FirstOrDefault()?.Volume ?? 100; + // We want to ignore custom sample banks and volume when not encoding to the mania game mode, + // because they cause unexpected results in the editor and are already satisfied by the control points. + if (onlineRulesetID != 3) + { + customSampleBank = "0"; + volume = 0; + } + sb.Append(':'); sb.Append(FormattableString.Invariant($"{customSampleBank}:")); sb.Append(FormattableString.Invariant($"{volume}:")); From 27cfadca16476bc9cbe069294bbc3b141c9cdcc7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 14:14:57 +0200 Subject: [PATCH 077/128] add sample info to Banana and SpinnerBonusTick --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 8 +++----- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 6 ------ osu.Game/Rulesets/Objects/HitObject.cs | 2 +- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index af03c9acab..e137204c32 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -22,11 +22,9 @@ namespace osu.Game.Rulesets.Catch.Objects public override Judgement CreateJudgement() => new CatchBananaJudgement(); - private static readonly List samples = new List { new BananaHitSampleInfo() }; - - public Banana() + public Banana(int volume = 100) { - Samples = samples; + Samples = new List { new BananaHitSampleInfo(volume) }; } // override any external colour changes with banananana @@ -53,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Objects public override IEnumerable LookupNames => lookup_names; - public BananaHitSampleInfo(int volume = 0) + public BananaHitSampleInfo(int volume = 100) : base(string.Empty, volume: volume) { } diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index b45f95a8e6..08febeabbf 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects { cancellationToken.ThrowIfCancellationRequested(); - AddNested(new Banana + AddNested(new Banana(GetSampleInfo().Volume) { StartTime = time, BananaIndex = i, diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 55924c19c9..df5898fd67 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Objects AddNested(i < SpinsRequired ? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration } - : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration }); + : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration, Samples = new[] { GetSampleInfo("spinnerbonus") } }); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 81cdf5755b..00ceccaf7b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -11,11 +10,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerBonusTick : SpinnerTick { - public SpinnerBonusTick() - { - Samples.Add(new HitSampleInfo("spinnerbonus")); - } - public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 774ff9dc1d..a4cb976d50 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Objects /// /// The name of the sample. /// A populated . - protected HitSampleInfo GetSampleInfo(string sampleName) + protected HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) { var hitnormalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); return hitnormalSample == null ? new HitSampleInfo(sampleName) : hitnormalSample.With(newName: sampleName); From 8302bb1f37b5c5c18d85134fc0a5834b7c4caaa9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 14:56:29 +0200 Subject: [PATCH 078/128] dont encode custom sample bank for objects without legacy samples --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 28f3a8d951..343dd7b082 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -173,9 +172,6 @@ namespace osu.Game.Beatmaps.Formats private void handleControlPoints(TextWriter writer) { - if (beatmap.ControlPointInfo.Groups.Count == 0) - return; - var legacyControlPoints = new LegacyControlPointInfo(); foreach (var point in beatmap.ControlPointInfo.AllControlPoints) legacyControlPoints.Add(point.Time, point.DeepClone()); @@ -199,6 +195,8 @@ namespace osu.Game.Beatmaps.Formats legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed }); } + int lastCustomSampleIndex = 0; + foreach (var group in legacyControlPoints.Groups) { var groupTimingPoint = group.ControlPoints.OfType().FirstOrDefault(); @@ -227,6 +225,12 @@ namespace osu.Game.Beatmaps.Formats // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); + // Inherit the previous sample bank if the current sample bank is not set + int customSampleBank = toLegacyCustomSampleBank(tempHitSample); + if (customSampleBank < 0) + customSampleBank = lastCustomSampleIndex; + lastCustomSampleIndex = customSampleBank; + // Convert effect flags to the legacy format LegacyEffectFlags effectFlags = LegacyEffectFlags.None; if (effectPoint.KiaiMode) @@ -236,7 +240,7 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{timingPoint.TimeSignature.Numerator},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); - writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); + writer.Write(FormattableString.Invariant($"{customSampleBank},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},")); writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); @@ -276,7 +280,8 @@ namespace osu.Game.Beatmaps.Formats int volume = hitObject.Samples.Max(o => o.Volume); int customIndex = hitObject.Samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo) ? hitObject.Samples.OfType().Max(o => o.CustomSampleBank) - : 0; + : -1; + yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex }; } @@ -516,7 +521,7 @@ namespace osu.Game.Beatmaps.Formats if (!banksOnly) { - string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))); + int customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))); string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty; int volume = samples.FirstOrDefault()?.Volume ?? 100; @@ -524,7 +529,7 @@ namespace osu.Game.Beatmaps.Formats // because they cause unexpected results in the editor and are already satisfied by the control points. if (onlineRulesetID != 3) { - customSampleBank = "0"; + customSampleBank = 0; volume = 0; } @@ -580,12 +585,12 @@ namespace osu.Game.Beatmaps.Formats } } - private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo) + private int toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo) { if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy) - return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture); + return legacy.CustomSampleBank; - return "0"; + return 0; } } } From 1dc34ee25de83fd4c2bc73151bfd3fde607519d0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 15:48:54 +0200 Subject: [PATCH 079/128] fix infinite repeat count when adjusting length of 0 length slider --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 4e5087c004..50f941a1e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -414,7 +414,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline double lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); int proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1); - if (proposedCount == repeatHitObject.RepeatCount) + if (proposedCount == repeatHitObject.RepeatCount || lengthOfOneRepeat == 0) return; repeatHitObject.RepeatCount = proposedCount; From dbb2a8980b001c3ca97f31d3b7b9431ea1d30f7c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 15:56:23 +0200 Subject: [PATCH 080/128] add test --- .../TestSceneTimelineHitObjectBlueprint.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 709d796e97..932e45b1a6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -77,5 +77,38 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("object has non-zero duration", () => EditorBeatmap.HitObjects.OfType().Single().Duration > 0); } + + [Test] + public void TestDisallowRepeatsOnZeroDurationObjects() + { + DragArea dragArea; + + AddStep("add zero length slider", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new Slider + { + Position = new Vector2(256, 256), + StartTime = 2700 + }); + }); + + AddStep("hold down drag bar", () => + { + // distinguishes between the actual drag bar and its "underlay shadow". + dragArea = this.ChildrenOfType().Single(bar => bar.HandlePositionalInput); + InputManager.MoveMouseTo(dragArea); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("try to extend drag bar", () => + { + var blueprint = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(blueprint.SelectionQuad.TopLeft + new Vector2(100, 0)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("object has zero repeats", () => EditorBeatmap.HitObjects.OfType().Single().RepeatCount == 0); + } } } From 8c21fddb5e1d98a0158e664b44b0c7331c2338f7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 16:53:38 +0200 Subject: [PATCH 081/128] remove all redundancies from encoded control points --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 72 ++++++++++++++----- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 343dd7b082..6b228a56a1 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -170,6 +171,24 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); } + private struct LegacyControlPointProperties + { + internal double SliderVelocity { get; set; } + internal int TimingSignature { get; init; } + internal int SampleBank { get; init; } + internal int CustomSampleBank { get; init; } + internal int SampleVolume { get; init; } + internal LegacyEffectFlags EffectFlags { get; init; } + + internal bool IsRedundant(LegacyControlPointProperties other) => + SliderVelocity == other.SliderVelocity && + TimingSignature == other.TimingSignature && + SampleBank == other.SampleBank && + CustomSampleBank == other.CustomSampleBank && + SampleVolume == other.SampleVolume && + EffectFlags == other.EffectFlags; + } + private void handleControlPoints(TextWriter writer) { var legacyControlPoints = new LegacyControlPointInfo(); @@ -195,41 +214,44 @@ namespace osu.Game.Beatmaps.Formats legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed }); } - int lastCustomSampleIndex = 0; + LegacyControlPointProperties lastControlPointProperties = new LegacyControlPointProperties(); foreach (var group in legacyControlPoints.Groups) { var groupTimingPoint = group.ControlPoints.OfType().FirstOrDefault(); + var controlPointProperties = getLegacyControlPointProperties(group, groupTimingPoint != null); // If the group contains a timing control point, it needs to be output separately. if (groupTimingPoint != null) { writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},")); writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},")); - outputControlPointAt(groupTimingPoint.Time, true); + outputControlPointAt(controlPointProperties, true); + lastControlPointProperties = controlPointProperties; + lastControlPointProperties.SliderVelocity = 1; } + if (controlPointProperties.IsRedundant(lastControlPointProperties)) + continue; + // Output any remaining effects as secondary non-timing control point. - var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time); writer.Write(FormattableString.Invariant($"{group.Time},")); - writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SliderVelocity},")); - outputControlPointAt(group.Time, false); + writer.Write(FormattableString.Invariant($"{-100 / controlPointProperties.SliderVelocity},")); + outputControlPointAt(controlPointProperties, false); + lastControlPointProperties = controlPointProperties; } - void outputControlPointAt(double time, bool isTimingPoint) + LegacyControlPointProperties getLegacyControlPointProperties(ControlPointGroup group, bool updateSampleBank) { + double time = group.Time; + var timingPoint = legacyControlPoints.TimingPointAt(time); + var difficultyPoint = legacyControlPoints.DifficultyPointAt(time); var samplePoint = legacyControlPoints.SamplePointAt(time); var effectPoint = legacyControlPoints.EffectPointAt(time); - var timingPoint = legacyControlPoints.TimingPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); - - // Inherit the previous sample bank if the current sample bank is not set int customSampleBank = toLegacyCustomSampleBank(tempHitSample); - if (customSampleBank < 0) - customSampleBank = lastCustomSampleIndex; - lastCustomSampleIndex = customSampleBank; // Convert effect flags to the legacy format LegacyEffectFlags effectFlags = LegacyEffectFlags.None; @@ -238,12 +260,26 @@ namespace osu.Game.Beatmaps.Formats if (timingPoint.OmitFirstBarLine) effectFlags |= LegacyEffectFlags.OmitFirstBarLine; - writer.Write(FormattableString.Invariant($"{timingPoint.TimeSignature.Numerator},")); - writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); - writer.Write(FormattableString.Invariant($"{customSampleBank},")); - writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); - writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},")); - writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); + return new LegacyControlPointProperties + { + SliderVelocity = difficultyPoint.SliderVelocity, + TimingSignature = timingPoint.TimeSignature.Numerator, + SampleBank = updateSampleBank ? (int)toLegacySampleBank(tempHitSample.Bank) : lastControlPointProperties.SampleBank, + // Inherit the previous custom sample bank if the current custom sample bank is not set + CustomSampleBank = customSampleBank >= 0 ? customSampleBank : lastControlPointProperties.CustomSampleBank, + SampleVolume = tempHitSample.Volume, + EffectFlags = effectFlags + }; + } + + void outputControlPointAt(LegacyControlPointProperties controlPoint, bool isTimingPoint) + { + writer.Write(FormattableString.Invariant($"{controlPoint.TimingSignature.ToString(CultureInfo.InvariantCulture)},")); + writer.Write(FormattableString.Invariant($"{controlPoint.SampleBank.ToString(CultureInfo.InvariantCulture)},")); + writer.Write(FormattableString.Invariant($"{controlPoint.CustomSampleBank.ToString(CultureInfo.InvariantCulture)},")); + writer.Write(FormattableString.Invariant($"{controlPoint.SampleVolume.ToString(CultureInfo.InvariantCulture)},")); + writer.Write(FormattableString.Invariant($"{(isTimingPoint ? "1" : "0")},")); + writer.Write(FormattableString.Invariant($"{((int)controlPoint.EffectFlags).ToString(CultureInfo.InvariantCulture)}")); writer.WriteLine(); } From 8ab3a87b13b7a7d0efce52d8db67b2e8af9f6a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 May 2023 17:13:09 +0200 Subject: [PATCH 082/128] Add failing test case covering online ID reset on save This test scene passes at e58e1151f3c610d8019e1f1e408a1cb55d204c24 and fails at current master, due to an inadvertent regression caused by e72f103c1759e61c3afa7080f962c639265996c3. As it turns out, the online lookup flow that was causing UI thread freezes when saving beatmaps in the editor, was also responsible for resetting the online ID of locally-modified beatmaps if online lookup failed. --- ...TestSceneLocallyModifyingOnlineBeatmaps.cs | 56 +++++++++++++++++++ .../Tests/Visual/EditorSavingTestScene.cs | 27 ++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneLocallyModifyingOnlineBeatmaps.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLocallyModifyingOnlineBeatmaps.cs b/osu.Game.Tests/Visual/Editing/TestSceneLocallyModifyingOnlineBeatmaps.cs new file mode 100644 index 0000000000..be9bbcfdd8 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneLocallyModifyingOnlineBeatmaps.cs @@ -0,0 +1,56 @@ +// 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 System.Net; +using NUnit.Framework; +using osu.Framework.Extensions; +using osu.Game.Database; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneLocallyModifyingOnlineBeatmaps : EditorSavingTestScene + { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + public override void SetUpSteps() + { + CreateInitialBeatmap = () => + { + var importedSet = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).GetResultSafely(); + return Game.BeatmapManager.GetWorkingBeatmap(importedSet!.Value.Beatmaps.First()); + }; + + base.SetUpSteps(); + } + + [Test] + public void TestLocallyModifyingOnlineBeatmap() + { + AddAssert("editor beatmap has online ID", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.GreaterThan(0)); + + AddStep("delete first hitobject", () => EditorBeatmap.RemoveAt(0)); + + AddStep("mock online lookup failure", () => + { + dummyAPI.HandleRequest = req => + { + if (req is GetBeatmapRequest) + { + req.TriggerFailure(new APIException("Beatmap not found", new WebException("NotFound"))); + return true; + } + + return false; + }; + }); + SaveEditor(); + + ReloadEditorToSameBeatmap(); + AddAssert("editor beatmap online ID reset", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.EqualTo(-1)); + } + } +} diff --git a/osu.Game/Tests/Visual/EditorSavingTestScene.cs b/osu.Game/Tests/Visual/EditorSavingTestScene.cs index cd9e9e1d52..78188d7cf7 100644 --- a/osu.Game/Tests/Visual/EditorSavingTestScene.cs +++ b/osu.Game/Tests/Visual/EditorSavingTestScene.cs @@ -3,9 +3,12 @@ #nullable disable +using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Input; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; @@ -24,18 +27,27 @@ namespace osu.Game.Tests.Visual protected EditorBeatmap EditorBeatmap => (EditorBeatmap)Editor.Dependencies.Get(typeof(EditorBeatmap)); + [CanBeNull] + protected Func CreateInitialBeatmap { get; set; } + [SetUpSteps] public override void SetUpSteps() { base.SetUpSteps(); - AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); + if (CreateInitialBeatmap == null) + AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); + else + { + AddStep("set test beatmap", () => Game.Beatmap.Value = CreateInitialBeatmap?.Invoke()); + } PushAndConfirm(() => new EditorLoader()); AddUntilStep("wait for editor load", () => Editor?.IsLoaded == true); - AddUntilStep("wait for metadata screen load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + if (CreateInitialBeatmap == null) + AddUntilStep("wait for metadata screen load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten. @@ -50,6 +62,14 @@ namespace osu.Game.Tests.Visual protected void ReloadEditorToSameBeatmap() { + Guid beatmapSetGuid = Guid.Empty; + Guid beatmapGuid = Guid.Empty; + + AddStep("Store beatmap GUIDs", () => + { + beatmapSetGuid = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID; + beatmapGuid = EditorBeatmap.BeatmapInfo.ID; + }); AddStep("Exit", () => InputManager.Key(Key.Escape)); AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); @@ -59,7 +79,8 @@ namespace osu.Game.Tests.Visual PushAndConfirm(() => songSelect = new PlaySongSelect()); AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded); - AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); + AddStep("Present same beatmap", () => Game.PresentBeatmap(Game.BeatmapManager.QueryBeatmapSet(set => set.ID == beatmapSetGuid)!.Value, beatmap => beatmap.ID == beatmapGuid)); + AddUntilStep("Wait for beatmap selected", () => Game.Beatmap.Value.BeatmapInfo.ID == beatmapGuid); AddStep("Open options", () => InputManager.Key(Key.F3)); AddStep("Enter editor", () => InputManager.Key(Key.Number5)); From f470b2c9cc379b24be498b4865f0f82f6fa41a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 May 2023 17:24:58 +0200 Subject: [PATCH 083/128] Always reset online info when saving local beatmap --- osu.Game/Beatmaps/BeatmapManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6af6a25579..3f2ab9b391 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -440,6 +440,7 @@ namespace osu.Game.Beatmaps beatmapInfo.LastLocalUpdate = DateTimeOffset.Now; beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; + beatmapInfo.ResetOnlineInfo(); AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); From 1fb4c814f4446eefa768e9f2f0f73af0334c64f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 May 2023 17:31:55 +0200 Subject: [PATCH 084/128] Remove no longer needed API call mocking The online ID will be reset unconditionally after any local change is made to any beatmap. That behaviour no longer depends on online lookups succeeding or failing. This may change at a later date when beatmap submission is integrated into lazer - the idea is that online IDs would get re-populated on local beatmaps once they are submitted to web. --- ...TestSceneLocallyModifyingOnlineBeatmaps.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLocallyModifyingOnlineBeatmaps.cs b/osu.Game.Tests/Visual/Editing/TestSceneLocallyModifyingOnlineBeatmaps.cs index be9bbcfdd8..7f9a69833c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneLocallyModifyingOnlineBeatmaps.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneLocallyModifyingOnlineBeatmaps.cs @@ -2,20 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using System.Net; using NUnit.Framework; using osu.Framework.Extensions; using osu.Game.Database; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneLocallyModifyingOnlineBeatmaps : EditorSavingTestScene { - private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; - public override void SetUpSteps() { CreateInitialBeatmap = () => @@ -33,20 +28,6 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("editor beatmap has online ID", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.GreaterThan(0)); AddStep("delete first hitobject", () => EditorBeatmap.RemoveAt(0)); - - AddStep("mock online lookup failure", () => - { - dummyAPI.HandleRequest = req => - { - if (req is GetBeatmapRequest) - { - req.TriggerFailure(new APIException("Beatmap not found", new WebException("NotFound"))); - return true; - } - - return false; - }; - }); SaveEditor(); ReloadEditorToSameBeatmap(); From cf5211aec922852673c77dcedae081c89ca005e1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 19:22:52 +0200 Subject: [PATCH 085/128] Enable current distance snap when exactly on a hit object --- osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs index aa47b4f424..09c6af3820 100644 --- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Edit private (HitObject before, HitObject after)? getObjectsOnEitherSideOfCurrentTime() { - HitObject lastBefore = Playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime <= EditorClock.CurrentTime)?.HitObject; + HitObject lastBefore = Playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime < EditorClock.CurrentTime)?.HitObject; if (lastBefore == null) return null; From 436ebdcfcb1d556c6bad05dea472aa9f4f41959a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 May 2023 19:06:25 +0200 Subject: [PATCH 086/128] Fix beatmap leaderboard test failure Because the online info reset (which includes online ID reset) was happening after encoding, `TestSceneBeatmapLeaderboard.TestLocalScoresDisplayOnBeatmapEdit()` started failing, as the hash no longer matched expectations after the first save of the map. --- osu.Game/Beatmaps/BeatmapManager.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 3f2ab9b391..ae62564b0d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -415,6 +415,13 @@ namespace osu.Game.Beatmaps // All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding. beatmapContent.BeatmapInfo = beatmapInfo; + // Since now this is a locally-modified beatmap, we also set all relevant flags to indicate this. + // Importantly, the `ResetOnlineInfo()` call must happen before encoding, as online ID is encoded into the `.osu` file, + // which influences the beatmap checksums. + beatmapInfo.LastLocalUpdate = DateTimeOffset.Now; + beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; + beatmapInfo.ResetOnlineInfo(); + using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) @@ -438,10 +445,6 @@ namespace osu.Game.Beatmaps beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); beatmapInfo.Hash = stream.ComputeSHA2Hash(); - beatmapInfo.LastLocalUpdate = DateTimeOffset.Now; - beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; - beatmapInfo.ResetOnlineInfo(); - AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); updateHashAndMarkDirty(setInfo); From 87db89114368d55a4029ff45109a0a9b9002c268 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 13:12:08 +0900 Subject: [PATCH 087/128] Adjust test to reliabily fail --- .../Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 932e45b1a6..08e036248b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -105,9 +105,10 @@ namespace osu.Game.Tests.Visual.Editing { var blueprint = this.ChildrenOfType().Single(); InputManager.MoveMouseTo(blueprint.SelectionQuad.TopLeft + new Vector2(100, 0)); - InputManager.ReleaseButton(MouseButton.Left); }); + AddStep("release button", () => InputManager.PressButton(MouseButton.Left)); + AddAssert("object has zero repeats", () => EditorBeatmap.HitObjects.OfType().Single().RepeatCount == 0); } } From 63890ef6feb9b8fffd28152545aaf88d0aefe9ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 13:24:19 +0900 Subject: [PATCH 088/128] Fix audio offset tooltip potentially showing "-0 ms" Closes https://github.com/ppy/osu/issues/23339. --- osu.Game/Graphics/UserInterface/TimeSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/TimeSlider.cs b/osu.Game/Graphics/UserInterface/TimeSlider.cs index e4058827f3..e6e7ae9305 100644 --- a/osu.Game/Graphics/UserInterface/TimeSlider.cs +++ b/osu.Game/Graphics/UserInterface/TimeSlider.cs @@ -12,6 +12,6 @@ namespace osu.Game.Graphics.UserInterface /// public partial class TimeSlider : RoundedSliderBar { - public override LocalisableString TooltipText => $"{Current.Value:N0} ms"; + public override LocalisableString TooltipText => $"{base.TooltipText} ms"; } } From e808e7316b320c3b9f4105ed2f79081bf8203283 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 13:29:30 +0900 Subject: [PATCH 089/128] Mark delegate value unused and add comment to avoid future regression --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 46d3620c9c..38ae8c68cb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -242,7 +242,7 @@ namespace osu.Game.Overlays.Mods if (AllowCustomisation) ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); - SelectedMods.BindValueChanged(val => + SelectedMods.BindValueChanged(_ => { updateMultiplier(); updateFromExternalSelection(); @@ -252,6 +252,10 @@ namespace osu.Game.Overlays.Mods if (AllowCustomisation) { + // Importantly, use SelectedMods.Value here (and not the ValueChanged NewValue) as the latter can + // potentially be stale, due to complexities in the way change trackers work. + // + // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); } From 37a5dde8592c48edebdc3ffaa36da96da75d226c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 13:47:49 +0900 Subject: [PATCH 090/128] Fix `BeatmapAttributeText` not supporting unicode artist/title --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 2c16a67cac..6523039a3f 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -84,8 +84,8 @@ namespace osu.Game.Skinning.Components private void updateBeatmapContent(WorkingBeatmap workingBeatmap) { - valueDictionary[BeatmapAttribute.Title] = workingBeatmap.BeatmapInfo.Metadata.Title; - valueDictionary[BeatmapAttribute.Artist] = workingBeatmap.BeatmapInfo.Metadata.Artist; + valueDictionary[BeatmapAttribute.Title] = new RomanisableString(workingBeatmap.BeatmapInfo.Metadata.TitleUnicode, workingBeatmap.BeatmapInfo.Metadata.Title); + valueDictionary[BeatmapAttribute.Artist] = new RomanisableString(workingBeatmap.BeatmapInfo.Metadata.ArtistUnicode, workingBeatmap.BeatmapInfo.Metadata.Artist); valueDictionary[BeatmapAttribute.DifficultyName] = workingBeatmap.BeatmapInfo.DifficultyName; valueDictionary[BeatmapAttribute.Creator] = workingBeatmap.BeatmapInfo.Metadata.Author.Username; valueDictionary[BeatmapAttribute.Length] = TimeSpan.FromMilliseconds(workingBeatmap.BeatmapInfo.Length).ToFormattedDuration(); From ad40099e3280d0929ce13b2adca488354bef9f81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 14:00:52 +0900 Subject: [PATCH 091/128] Ensure negative sign is only applied when the post-rounded result is negative --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 0c36d73085..0e26029ffa 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -97,7 +97,9 @@ namespace osu.Game.Graphics.UserInterface // Find the number of significant digits (we could have less than 5 after normalize()) int significantDigits = FormatUtils.FindPrecision(decimalPrecision); - return floatValue.ToString($"N{significantDigits}"); + string negativeSign = Math.Round(floatValue, significantDigits) < 0 ? "-" : string.Empty; + + return $"{negativeSign}{Math.Abs(floatValue).ToString($"N{significantDigits}")}"; } /// From 736be6a73ba2c9ff5332b39f828f0310a8d76851 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 14:11:16 +0900 Subject: [PATCH 092/128] Refactor slightly for readability --- osu.Game/Overlays/Music/PlaylistItem.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 4a39cc06c8..90fdfd0491 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Music var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - titlePart.DrawablePartsRecreated += _ => updateSelectionState(SelectedSet.Value, instant: true); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(SelectedSet.Value, applyImmediately: true); text.AddText(@" "); // to separate the title from the artist. text.AddText(artist, sprite => @@ -66,24 +66,25 @@ namespace osu.Game.Overlays.Music sprite.Padding = new MarginPadding { Top = 1 }; }); - SelectedSet.BindValueChanged(set => updateSelectionState(set.NewValue, instant: false)); - updateSelectionState(SelectedSet.Value, instant: true); + SelectedSet.BindValueChanged(set => updateSelectionState(set.NewValue)); + updateSelectionState(SelectedSet.Value, applyImmediately: true); }); } private bool selected; - private void updateSelectionState(Live selectedSet, bool instant) + private void updateSelectionState(Live selectedSet, bool applyImmediately = false) { - bool newSelected = selectedSet?.Equals(Model) == true; + bool wasSelected = selected; + selected = selectedSet?.Equals(Model) == true; - if (newSelected == selected && !instant) // instant updates should forcibly set correct state regardless of previous state. + // Immediate updates should forcibly set correct state regardless of previous state. + // This ensures that the initial state is correctly applied. + if (wasSelected == selected && !applyImmediately) return; - selected = newSelected; - foreach (Drawable s in titlePart.Drawables) - s.FadeColour(selected ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); + s.FadeColour(selected ? colours.Yellow : Color4.White, applyImmediately ? 0 : FADE_DURATION); } protected override Drawable CreateContent() => new DelayedLoadWrapper(text = new OsuTextFlowContainer From 7a840e1d8227a3e2dd5073afd27305c47dc7f494 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 14:29:26 +0900 Subject: [PATCH 093/128] Remove unnecessary `TransferValue` spec --- osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 0515e8dc97..ad47039579 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -171,7 +171,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Current = scalingBackgroundDim, KeyboardStep = 0.01f, DisplayAsPercentage = true, - TransferValueOnCommit = false }, } }, From ab4f66fad331aa7d048f8f1365ec12f25071098b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 14:31:39 +0900 Subject: [PATCH 094/128] Minor readability refactors --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- .../Settings/Sections/Graphics/LayoutSettings.cs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index bd52c8bb32..84eb382d34 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -155,7 +155,7 @@ namespace osu.Game.Graphics.Containers private bool requiresBackgroundVisible => (scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1) - && scalingMenuBackgroundDim.Value != 1f; + && scalingMenuBackgroundDim.Value < 1; private void updateSize() { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index ad47039579..2e26d15105 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -230,11 +230,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics if (s == dimSlider) { s.CanBeShown.Value = scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays; - return; } - - s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything; - s.CanBeShown.Value = scalingMode.Value != ScalingMode.Off; + else + { + s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything; + s.CanBeShown.Value = scalingMode.Value != ScalingMode.Off; + } }); } } From 57f48e070359f65100e33838091ee72f28d6cc68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 14:33:57 +0900 Subject: [PATCH 095/128] Start colour black to ensure initial appear transition doesn't look silly --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 84eb382d34..c47aba2f0c 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -15,6 +15,7 @@ using osu.Game.Configuration; using osu.Game.Screens; using osu.Game.Screens.Backgrounds; using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.Containers { @@ -169,6 +170,7 @@ namespace osu.Game.Graphics.Containers AddInternal(backgroundStack = new BackgroundScreenStack { Alpha = 0, + Colour = Color4.Black, Depth = float.MaxValue }); From 8a536c1cdb75496ad56b056baa83fc2d14376deb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 15:09:24 +0900 Subject: [PATCH 096/128] Fix non-block namespace usage --- .../Timeline/HitObjectPointPiece.cs | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs index f7854705a4..4b357d3a62 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -11,51 +11,52 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Compose.Components.Timeline; - -public partial class HitObjectPointPiece : CircularContainer +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - protected OsuSpriteText Label { get; private set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) + public partial class HitObjectPointPiece : CircularContainer { - AutoSizeAxes = Axes.Both; + protected OsuSpriteText Label { get; private set; } - Color4 colour = GetRepresentingColour(colours); - - InternalChildren = new Drawable[] + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - new Container - { - AutoSizeAxes = Axes.X, - Height = 16, - Masking = true, - CornerRadius = 8, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new Drawable[] - { - new Box - { - Colour = colour, - RelativeSizeAxes = Axes.Both, - }, - Label = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Padding = new MarginPadding(5), - Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), - Colour = colours.B5, - } - } - }, - }; - } + AutoSizeAxes = Axes.Both; - protected virtual Color4 GetRepresentingColour(OsuColour colours) - { - return colours.Yellow; + Color4 colour = GetRepresentingColour(colours); + + InternalChildren = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.X, + Height = 16, + Masking = true, + CornerRadius = 8, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] + { + new Box + { + Colour = colour, + RelativeSizeAxes = Axes.Both, + }, + Label = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5), + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + Colour = colours.B5, + } + } + }, + }; + } + + protected virtual Color4 GetRepresentingColour(OsuColour colours) + { + return colours.Yellow; + } } } From 67f83f246b1c65c62416a24375ef580139d046dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 15:36:01 +0900 Subject: [PATCH 097/128] Add more padding around playfield in editor to avoid overlap with tool areas Closes #23130. --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 9 +++++++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 12 ++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 7a70257f3a..ff1e208186 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -13,8 +13,8 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Utils; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -62,7 +62,12 @@ namespace osu.Game.Rulesets.Osu.Edit private void load() { // Give a bit of breathing room around the playfield content. - PlayfieldContentContainer.Padding = new MarginPadding(10); + PlayfieldContentContainer.Padding = new MarginPadding + { + Vertical = 10, + Left = TOOLBOX_CONTRACTED_SIZE_LEFT + 10, + Right = TOOLBOX_CONTRACTED_SIZE_RIGHT + 10, + }; LayerBelowRuleset.AddRange(new Drawable[] { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 653861c11c..e2dbd2acdc 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -116,6 +116,11 @@ namespace osu.Game.Rulesets.Edit PlayfieldContentContainer = new Container { Name = "Content", + Padding = new MarginPadding + { + Left = TOOLBOX_CONTRACTED_SIZE_LEFT, + Right = TOOLBOX_CONTRACTED_SIZE_RIGHT, + }, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -138,7 +143,7 @@ namespace osu.Game.Rulesets.Edit Colour = colourProvider.Background5, RelativeSizeAxes = Axes.Both, }, - LeftToolbox = new ExpandingToolboxContainer(60, 200) + LeftToolbox = new ExpandingToolboxContainer(TOOLBOX_CONTRACTED_SIZE_LEFT, 200) { Children = new Drawable[] { @@ -173,7 +178,7 @@ namespace osu.Game.Rulesets.Edit Colour = colourProvider.Background5, RelativeSizeAxes = Axes.Both, }, - RightToolbox = new ExpandingToolboxContainer(130, 250) + RightToolbox = new ExpandingToolboxContainer(TOOLBOX_CONTRACTED_SIZE_RIGHT, 250) { Child = new EditorToolboxGroup("inspector") { @@ -450,6 +455,9 @@ namespace osu.Game.Rulesets.Edit [Cached] public abstract partial class HitObjectComposer : CompositeDrawable, IPositionSnapProvider { + public const float TOOLBOX_CONTRACTED_SIZE_LEFT = 60; + public const float TOOLBOX_CONTRACTED_SIZE_RIGHT = 130; + public readonly Ruleset Ruleset; protected HitObjectComposer(Ruleset ruleset) From c2ad8c23201814a3ce3103584e2bacc51c30b020 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 2 May 2023 08:41:30 +0200 Subject: [PATCH 098/128] Fix comment 1 osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs Co-authored-by: Dean Herbert --- osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs index 5de7d348c5..c587654cda 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types public interface IHasGenerateTicks { /// - /// Whether or not slider ticks should be generated at this control point. + /// Whether or not slider ticks should be generated by this object. /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). /// public bool GenerateTicks { get; set; } From 2e018c8b0696b2394465ef31d1f49aad7c514627 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 2 May 2023 08:41:47 +0200 Subject: [PATCH 099/128] Fix comment 2 osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs Co-authored-by: Dean Herbert --- osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs index c587654cda..3ac8b8a086 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs @@ -4,7 +4,7 @@ namespace osu.Game.Rulesets.Objects.Types { /// - /// A type of which may or may not generate ticks. + /// A type of which explicitly specifies whether it should generate ticks. /// public interface IHasGenerateTicks { From bd72c67d68aa3bc4bc18f70cd0fd6a4c8c9eb9d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 15:47:35 +0900 Subject: [PATCH 100/128] Increase the rate of slider ball fade on argon skins to match other implementations --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs index d6ce793c7e..461b4a3b45 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs @@ -98,7 +98,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { - this.FadeOut(duration, Easing.OutQuint); + // intentionally pile on an extra FadeOut to make it happen much faster + this.FadeOut(duration / 4, Easing.OutQuint); icon.ScaleTo(defaultIconScale * icon_scale, duration, Easing.OutQuint); } } From e3c51b96526223674cada703130544646b768f97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 16:26:56 +0900 Subject: [PATCH 101/128] Add ability to toggle snaking in slider test scene --- .../TestSceneSlider.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 1e9f931b74..4ad78a3190 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -15,6 +15,10 @@ using osuTK.Graphics; using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; using osu.Game.Beatmaps.Legacy; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,6 +26,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Configuration; namespace osu.Game.Rulesets.Osu.Tests { @@ -30,6 +35,27 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; + private readonly BindableBool snakingIn = new BindableBool(); + private readonly BindableBool snakingOut = new BindableBool(); + + [SetUpSteps] + public void SetUpSteps() + { + AddToggleStep("toggle snaking", v => + { + snakingIn.Value = v; + snakingOut.Value = v; + }); + } + + [BackgroundDependencyLoader] + private void load() + { + var config = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull(); + config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn); + config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); + } + [Test] public void TestVariousSliders() { From 1a04be15c74b009bb9e329bf0d35b0031abe5328 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 16:27:17 +0900 Subject: [PATCH 102/128] Fix fade in delay for first slider end circle being incorrect when snaking disabled --- .../Objects/Drawables/DrawableSliderRepeat.cs | 11 +++++++---- .../Objects/Drawables/DrawableSliderTail.cs | 8 +++++++- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 5 +---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 3446d41fb4..d474db0526 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -87,12 +87,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateInitialTransforms() { + // When snaking in is enabled, the first end circle needs to be delayed until the snaking completes. + bool delayFadeIn = DrawableSlider.SliderBody!.SnakingIn.Value && HitObject.RepeatIndex == 0; + animDuration = Math.Min(300, HitObject.SpanDuration); - this.Animate( - d => d.FadeIn(animDuration), - d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 2, Easing.OutElasticHalf) - ); + this + .FadeOut() + .Delay(delayFadeIn ? Slider!.TimePreempt / 3f : 0) + .FadeIn(HitObject.RepeatIndex == 0 ? HitObject.TimeFadeIn : animDuration); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 2c1b68e05a..da4feae242 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -91,7 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - CirclePiece.FadeInFromZero(HitObject.TimeFadeIn); + // When snaking in is enabled, the first end circle needs to be delayed until the snaking completes. + bool delayFadeIn = DrawableSlider.SliderBody!.SnakingIn.Value && HitObject.RepeatIndex == 0; + + CirclePiece + .FadeOut() + .Delay(delayFadeIn ? Slider!.TimePreempt / 3f : 0) + .FadeIn(HitObject.TimeFadeIn); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index 35bec92354..b5374333c9 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -39,11 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects } else { - // taken from osu-stable - const float first_end_circle_preempt_adjust = 2 / 3f; - // The first end circle should fade in with the slider. - TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt * first_end_circle_preempt_adjust; + TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt; } } From a619812cab7f881c2968db977e2bb5322299b03e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 16:36:43 +0900 Subject: [PATCH 103/128] Fix nullability and remove extra preempt from `SliderEndCircle` calculation --- .../Objects/Drawables/DrawableSliderRepeat.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index d474db0526..fc4863f164 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -88,13 +88,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateInitialTransforms() { // When snaking in is enabled, the first end circle needs to be delayed until the snaking completes. - bool delayFadeIn = DrawableSlider.SliderBody!.SnakingIn.Value && HitObject.RepeatIndex == 0; + bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0; animDuration = Math.Min(300, HitObject.SpanDuration); this .FadeOut() - .Delay(delayFadeIn ? Slider!.TimePreempt / 3f : 0) + .Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0) .FadeIn(HitObject.RepeatIndex == 0 ? HitObject.TimeFadeIn : animDuration); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index da4feae242..d9501f7d58 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -92,11 +92,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.UpdateInitialTransforms(); // When snaking in is enabled, the first end circle needs to be delayed until the snaking completes. - bool delayFadeIn = DrawableSlider.SliderBody!.SnakingIn.Value && HitObject.RepeatIndex == 0; + bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0; CirclePiece .FadeOut() - .Delay(delayFadeIn ? Slider!.TimePreempt / 3f : 0) + .Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0) .FadeIn(HitObject.TimeFadeIn); } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index b5374333c9..f52c3ab382 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects else { // The first end circle should fade in with the slider. - TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt; + TimePreempt += StartTime - slider.StartTime; } } From 414b80d44e78ee5495aa18ae3afd7661b9fe32a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 17:00:54 +0900 Subject: [PATCH 104/128] Change flashlight depth in a more standard way --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 9527e8ab5d..215fc877dc 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Mods flashlight.RelativeSizeAxes = Axes.Both; flashlight.Colour = Color4.Black; + // Flashlight mods should always draw above any other mod adding overlays. + flashlight.Depth = float.MinValue; flashlight.Combo.BindTo(Combo); drawableRuleset.Overlays.Add(flashlight); - // Stop flashlight from being drawn underneath other mods that generate HitObjects. - drawableRuleset.Overlays.ChangeChildDepth(flashlight, float.MinValue); } protected abstract Flashlight CreateFlashlight(); From fb0e90913db89e8ffc80fc17056d8ed1f4cbfecc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 17:07:12 +0900 Subject: [PATCH 105/128] Ensure lifetime start is also updated when reverting judgements --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 0fc27c8f1d..bfc6c24fa5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Mods BubbleDrawable? lastBubble = bubbleContainer.OfType().LastOrDefault(); lastBubble?.ClearTransforms(); - lastBubble?.Expire(); + lastBubble?.Expire(true); }; } From 7830711c8e97b83852f705dbad25d50cc71a1604 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 17:07:22 +0900 Subject: [PATCH 106/128] Tidy up various code quality issues in `OsuModBubbles` --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index bfc6c24fa5..9ae38de7c9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -165,15 +165,15 @@ namespace osu.Game.Rulesets.Osu.Mods ColourInfo colourDarker = DrawableOsuHitObject.AccentColour.Value.Darken(0.1f); // The absolute length of the bubble's animation, can be used in fractions for animations of partial length - double getAnimationDuration = 1700 + Math.Pow(FadeTime, 1.07f); + double duration = 1700 + Math.Pow(FadeTime, 1.07f); // Main bubble scaling based on combo this.FadeTo(1) - .ScaleTo(MaxSize, getAnimationDuration * 0.8f) + .ScaleTo(MaxSize, duration * 0.8f) .Then() // Pop at the end of the bubbles life time - .ScaleTo(MaxSize * 1.5f, getAnimationDuration * 0.2f, Easing.OutQuint) - .FadeOut(getAnimationDuration * 0.2f, Easing.OutCirc).Expire(); + .ScaleTo(MaxSize * 1.5f, duration * 0.2f, Easing.OutQuint) + .FadeOut(duration * 0.2f, Easing.OutCirc).Expire(); if (!DrawableOsuHitObject.IsHit) return; @@ -182,28 +182,28 @@ namespace osu.Game.Rulesets.Osu.Mods colourBox.FadeColour(colourDarker); - content.TransformTo(nameof(BorderColour), colourDarker, getAnimationDuration * 0.3f, Easing.OutQuint); + content.TransformTo(nameof(BorderColour), colourDarker, duration * 0.3f, Easing.OutQuint); // Ripple effect utilises the border to reduce drawable count - content.TransformTo(nameof(BorderThickness), 2f, getAnimationDuration * 0.3f, Easing.OutQuint) + content.TransformTo(nameof(BorderThickness), 2f, duration * 0.3f, Easing.OutQuint) // Avoids transparency overlap issues during the bubble "pop" .Then().Schedule(() => content.BorderThickness = 0); } - private Vector2 getPosition(DrawableOsuHitObject drawableOsuHitObject) + private Vector2 getPosition(DrawableOsuHitObject drawableObject) { - switch (drawableOsuHitObject) + switch (drawableObject) { // SliderHeads are derived from HitCircles, // so we must handle them before to avoid them using the wrong positioning logic case DrawableSliderHead: - return drawableOsuHitObject.HitObject.Position; + return drawableObject.HitObject.Position; // Using hitobject position will cause issues with HitCircle placement due to stack leniency. case DrawableHitCircle: - return drawableOsuHitObject.Position; + return drawableObject.Position; default: - return drawableOsuHitObject.HitObject.Position; + return drawableObject.HitObject.Position; } } } From e44672bdd5f4f11ae0d309c20df29271d4203f05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 17:08:49 +0900 Subject: [PATCH 107/128] Avoid using `Schedule` in transforms (doesn't handle rewind well) --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 9ae38de7c9..12e2090f89 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -185,8 +185,9 @@ namespace osu.Game.Rulesets.Osu.Mods content.TransformTo(nameof(BorderColour), colourDarker, duration * 0.3f, Easing.OutQuint); // Ripple effect utilises the border to reduce drawable count content.TransformTo(nameof(BorderThickness), 2f, duration * 0.3f, Easing.OutQuint) + .Then() // Avoids transparency overlap issues during the bubble "pop" - .Then().Schedule(() => content.BorderThickness = 0); + .TransformTo(nameof(BorderThickness), 0f); } private Vector2 getPosition(DrawableOsuHitObject drawableObject) From 8160d562640f4c3f40d8d4b01a68306f8177e31c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 2 May 2023 11:51:05 +0300 Subject: [PATCH 108/128] Update test shaders --- .../Resources/Shaders/sh_TestFragment.fs | 15 ++++--- .../Resources/Shaders/sh_TestVertex.vs | 42 ++++++++----------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs b/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs index c70ad751be..99b85d0502 100644 --- a/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs +++ b/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs @@ -1,11 +1,14 @@ -#include "sh_Utils.h" +#define HIGH_PRECISION_VERTEX -varying mediump vec2 v_TexCoord; -varying mediump vec4 v_TexRect; +#include "sh_Utils.h" +#include "sh_Masking.h" + +layout(location = 2) in highp vec2 v_TexCoord; + +layout(location = 0) out vec4 o_Colour; void main(void) { - float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]); - gl_FragColor = hsv2rgb(vec4(hueValue, 1, 1, 1)); + highp float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]); + o_Colour = getRoundedColor(hsv2rgb(vec4(hueValue, 1, 1, 1)), v_TexCoord); } - diff --git a/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs b/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs index 4485356fa4..505554bb33 100644 --- a/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs +++ b/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs @@ -1,31 +1,25 @@ -#include "sh_Utils.h" +layout(location = 0) in highp vec2 m_Position; +layout(location = 1) in lowp vec4 m_Colour; +layout(location = 2) in highp vec2 m_TexCoord; +layout(location = 3) in highp vec4 m_TexRect; +layout(location = 4) in mediump vec2 m_BlendRange; -attribute highp vec2 m_Position; -attribute lowp vec4 m_Colour; -attribute mediump vec2 m_TexCoord; -attribute mediump vec4 m_TexRect; -attribute mediump vec2 m_BlendRange; - -varying highp vec2 v_MaskingPosition; -varying lowp vec4 v_Colour; -varying mediump vec2 v_TexCoord; -varying mediump vec4 v_TexRect; -varying mediump vec2 v_BlendRange; - -uniform highp mat4 g_ProjMatrix; -uniform highp mat3 g_ToMaskingSpace; +layout(location = 0) out highp vec2 v_MaskingPosition; +layout(location = 1) out lowp vec4 v_Colour; +layout(location = 2) out highp vec2 v_TexCoord; +layout(location = 3) out highp vec4 v_TexRect; +layout(location = 4) out mediump vec2 v_BlendRange; void main(void) { - // Transform from screen space to masking space. - highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0); - v_MaskingPosition = maskingPos.xy / maskingPos.z; + // Transform from screen space to masking space. + highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0); + v_MaskingPosition = maskingPos.xy / maskingPos.z; - v_Colour = m_Colour; - v_TexCoord = m_TexCoord; - v_TexRect = m_TexRect; - v_BlendRange = m_BlendRange; + v_Colour = m_Colour; + v_TexCoord = m_TexCoord; + v_TexRect = m_TexRect; + v_BlendRange = m_BlendRange; - gl_Position = gProjMatrix * vec4(m_Position, 1.0, 1.0); + gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0); } - From ba5088f71a57bf7d26eac4f6d319b9c0eb08d438 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 2 May 2023 11:55:05 +0300 Subject: [PATCH 109/128] Add missing ruleset shader tests --- osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 585a3f95e7..f0a9ce7beb 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -53,6 +53,8 @@ namespace osu.Game.Tests.Testing { Dependencies.Get().GetRawData(@"sh_TestVertex.vs"); Dependencies.Get().GetRawData(@"sh_TestFragment.fs"); + Dependencies.Get().Load(@"TestVertex", @"TestFragment"); + Dependencies.Get().Load(VertexShaderDescriptor.TEXTURE_2, @"TestFragment"); }); } From d2d81bb82c6fe9fcca7e7c8f4ac3879994aa8aa4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 2 May 2023 12:29:11 +0200 Subject: [PATCH 110/128] remove redundant zero check in sv calculation --- .../Edit/Blueprints/Components/EditablePath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 75ee0546dd..7a577f8a83 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components // // The value is clamped here by the bindable min and max values. // In case the required velocity is too large, the path is not preserved. - svBindable.Value = requiredVelocity == 0 ? 1 : Math.Ceiling(requiredVelocity / svToVelocityFactor); + svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor); path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); From 90d98cd3294ec30533017d83382d60525305f98c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 2 May 2023 12:41:39 +0200 Subject: [PATCH 111/128] remove constructor argument from Banana --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 8 +++++--- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index e137204c32..e4aa778902 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -22,9 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects public override Judgement CreateJudgement() => new CatchBananaJudgement(); - public Banana(int volume = 100) + private static readonly List samples = new List { new BananaHitSampleInfo() }; + + public Banana() { - Samples = new List { new BananaHitSampleInfo(volume) }; + Samples = samples; } // override any external colour changes with banananana @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Catch.Objects } } - private class BananaHitSampleInfo : HitSampleInfo, IEquatable + public class BananaHitSampleInfo : HitSampleInfo, IEquatable { private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" }; diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 08febeabbf..5bd4ac86f5 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.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.Collections.Generic; using System.Threading; +using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -35,10 +37,11 @@ namespace osu.Game.Rulesets.Catch.Objects { cancellationToken.ThrowIfCancellationRequested(); - AddNested(new Banana(GetSampleInfo().Volume) + AddNested(new Banana { StartTime = time, BananaIndex = i, + Samples = new List { new Banana.BananaHitSampleInfo(GetSampleInfo().Volume) } }); time += spacing; From 95badb9455218c2c2d1d755eb9bf1668c7624984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 May 2023 18:35:06 +0200 Subject: [PATCH 112/128] Adjust composer tests to new screen layout `TestSceneHitObjectComposer.TestPlacementFailsWhenClickingButton()` was attempting to cover the case of the user clicking a toolbox button which was in front of the playfield, and ensure that the click did not result in a placement. However, since the paddings in 67f83f246b1c65c62416a24375ef580139d046dc were added, it is impossible for a toolbox button to be in front of the playfield in the collapsed state, which the test was relying on. The test scenario is still however relevant in the case of the toolbox being expanded, as in that state the toolbux buttons may very well end up being in front of the playfield, and they still should not result in a hitobject being placed. To ensure that this is the case, add a few extra test steps ensuring that the toolbox is expanded first before trying to retrieve an overlapping button. --- .../Visual/Editing/TestSceneHitObjectComposer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 7ab0188114..9bdb9a513c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -108,12 +108,16 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").TriggerClick()); + ExpandingToolboxContainer toolboxContainer = null!; + + AddStep("move mouse to toolbox", () => InputManager.MoveMouseTo(toolboxContainer = hitObjectComposer.ChildrenOfType().First())); + AddUntilStep("toolbox is expanded", () => toolboxContainer.Expanded.Value); + AddUntilStep("wait for toolbox to expand", () => toolboxContainer.LatestTransformEndTime, () => Is.EqualTo(Time.Current)); + AddStep("move mouse to overlapping toggle button", () => { var playfield = hitObjectComposer.Playfield.ScreenSpaceDrawQuad; - var button = hitObjectComposer - .ChildrenOfType().First() - .ChildrenOfType().First(b => playfield.Contains(b.ScreenSpaceDrawQuad.Centre)); + var button = toolboxContainer.ChildrenOfType().First(b => playfield.Contains(b.ScreenSpaceDrawQuad.Centre)); InputManager.MoveMouseTo(button); }); From 9c4312b407bb208917a8abf42283018bdf47bd56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 12:37:07 +0900 Subject: [PATCH 113/128] Add support for flipping colour of reverse arrow on legacy default skin when combo colour is too bright --- .../Skinning/Legacy/LegacyReverseArrow.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index fbe094ef81..e6166e9441 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -3,11 +3,13 @@ using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -18,6 +20,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private Drawable proxy = null!; + private Bindable accentColour = null!; + + private bool textureIsDefaultSkin; + + private Drawable arrow = null!; + [BackgroundDependencyLoader] private void load(ISkinSource skinSource) { @@ -26,7 +34,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName; var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null); - InternalChild = skin?.GetAnimation(lookupName, true, true) ?? Empty(); + + InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true) ?? Empty()); + textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin; } protected override void LoadComplete() @@ -39,6 +49,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { drawableHitObject.HitObjectApplied += onHitObjectApplied; onHitObjectApplied(drawableHitObject); + + accentColour = drawableHitObject.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(c => + { + arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White; + }, true); } } From 16c624fb61a7dd02460e799dd48570162f3eb2a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:02:05 +0900 Subject: [PATCH 114/128] Ensure `static` banana samples are not mutated --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index e4aa778902..4c66c054e1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects public override Judgement CreateJudgement() => new CatchBananaJudgement(); - private static readonly List samples = new List { new BananaHitSampleInfo() }; + private static readonly IList default_banana_samples = new List { new BananaHitSampleInfo() }.AsReadOnly(); public Banana() { - Samples = samples; + Samples = default_banana_samples; } // override any external colour changes with banananana From 588a4e6196cc038928fa0d68481b72cae0cba4e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:19:13 +0900 Subject: [PATCH 115/128] Move pragma disable to top of `LegacyBeatmapDecoder` Makes more sense as it's used multiple times in the class. --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index d20f7e198b..9dcd97459f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -3,6 +3,8 @@ #nullable disable +#pragma warning disable 618 + using System; using System.Collections.Generic; using System.IO; @@ -102,9 +104,8 @@ namespace osu.Game.Beatmaps.Formats var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo; DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(hitObject.StartTime) : DifficultyControlPoint.DEFAULT; -#pragma warning disable 618 + if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) -#pragma warning restore 618 { hitObject.LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier; if (hitObject is IHasGenerateTicks hasGenerateTicks) @@ -494,9 +495,7 @@ namespace osu.Game.Beatmaps.Formats int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID; -#pragma warning disable 618 addControlPoint(time, new LegacyDifficultyControlPoint(onlineRulesetID, beatLength) -#pragma warning restore 618 { SliderVelocity = speedMultiplier, }, timingChange); From cca15f930c5ab8a4ef7a3558613bf4bf12f84d33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:26:50 +0900 Subject: [PATCH 116/128] Refactor `applyLegacyInfoAndDefaults` for legibility --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9dcd97459f..90be90fcaf 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -101,9 +101,14 @@ namespace osu.Game.Beatmaps.Formats private void applyLegacyInfoAndDefaults(HitObject hitObject) { - var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo; + DifficultyControlPoint difficultyControlPoint = DifficultyControlPoint.DEFAULT; + SampleControlPoint sampleControlPoint = SampleControlPoint.DEFAULT; - DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(hitObject.StartTime) : DifficultyControlPoint.DEFAULT; + if (beatmap.ControlPointInfo is LegacyControlPointInfo legacyInfo) + { + difficultyControlPoint = legacyInfo.DifficultyPointAt(hitObject.StartTime); + sampleControlPoint = legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency); + } if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) { @@ -117,18 +122,17 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; - hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); - if (hitObject is not IHasRepeats hasRepeats) return; - - for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + if (hitObject is IHasRepeats hasRepeats) { - double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; - sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(time) : SampleControlPoint.DEFAULT; + for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + { + double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; + sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT; - hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => sampleControlPoint.ApplyTo(o)).ToList(); + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => sampleControlPoint.ApplyTo(o)).ToList(); + } } } From 48fd99818ea448a6f21fa2ecb9e67bd8ef260e0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:30:45 +0900 Subject: [PATCH 117/128] Split out default and sample application --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 90be90fcaf..5e98025c9a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -95,20 +95,14 @@ namespace osu.Game.Beatmaps.Formats foreach (var hitObject in this.beatmap.HitObjects) { - applyLegacyInfoAndDefaults(hitObject); + applyDefaults(hitObject); + applySamples(hitObject); } } - private void applyLegacyInfoAndDefaults(HitObject hitObject) + private void applyDefaults(HitObject hitObject) { - DifficultyControlPoint difficultyControlPoint = DifficultyControlPoint.DEFAULT; - SampleControlPoint sampleControlPoint = SampleControlPoint.DEFAULT; - - if (beatmap.ControlPointInfo is LegacyControlPointInfo legacyInfo) - { - difficultyControlPoint = legacyInfo.DifficultyPointAt(hitObject.StartTime); - sampleControlPoint = legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency); - } + DifficultyControlPoint difficultyControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.DifficultyPointAt(hitObject.StartTime) ?? DifficultyControlPoint.DEFAULT; if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) { @@ -121,6 +115,11 @@ namespace osu.Game.Beatmaps.Formats hasSliderVelocity.SliderVelocity = difficultyControlPoint.SliderVelocity; hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + } + + private void applySamples(HitObject hitObject) + { + SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) ?? SampleControlPoint.DEFAULT; hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); @@ -129,9 +128,9 @@ namespace osu.Game.Beatmaps.Formats for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) { double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; - sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT; + var nodeSamplePoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT; - hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => sampleControlPoint.ApplyTo(o)).ToList(); + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => nodeSamplePoint.ApplyTo(o)).ToList(); } } } From f930c4bd0aaa4137ce2c2fbcbe5870a7d919fefb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:33:31 +0900 Subject: [PATCH 118/128] Move `struct` to bottom of file --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 6b228a56a1..7fbcca9adb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -92,7 +92,8 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); - writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints?.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); + writer.WriteLine(FormattableString.Invariant( + $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints?.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); @@ -171,24 +172,6 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); } - private struct LegacyControlPointProperties - { - internal double SliderVelocity { get; set; } - internal int TimingSignature { get; init; } - internal int SampleBank { get; init; } - internal int CustomSampleBank { get; init; } - internal int SampleVolume { get; init; } - internal LegacyEffectFlags EffectFlags { get; init; } - - internal bool IsRedundant(LegacyControlPointProperties other) => - SliderVelocity == other.SliderVelocity && - TimingSignature == other.TimingSignature && - SampleBank == other.SampleBank && - CustomSampleBank == other.CustomSampleBank && - SampleVolume == other.SampleVolume && - EffectFlags == other.EffectFlags; - } - private void handleControlPoints(TextWriter writer) { var legacyControlPoints = new LegacyControlPointInfo(); @@ -243,11 +226,10 @@ namespace osu.Game.Beatmaps.Formats LegacyControlPointProperties getLegacyControlPointProperties(ControlPointGroup group, bool updateSampleBank) { - double time = group.Time; - var timingPoint = legacyControlPoints.TimingPointAt(time); - var difficultyPoint = legacyControlPoints.DifficultyPointAt(time); - var samplePoint = legacyControlPoints.SamplePointAt(time); - var effectPoint = legacyControlPoints.EffectPointAt(time); + var timingPoint = legacyControlPoints.TimingPointAt(group.Time); + var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time); + var samplePoint = legacyControlPoints.SamplePointAt(group.Time); + var effectPoint = legacyControlPoints.EffectPointAt(group.Time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); @@ -628,5 +610,23 @@ namespace osu.Game.Beatmaps.Formats return 0; } + + private struct LegacyControlPointProperties + { + internal double SliderVelocity { get; set; } + internal int TimingSignature { get; init; } + internal int SampleBank { get; init; } + internal int CustomSampleBank { get; init; } + internal int SampleVolume { get; init; } + internal LegacyEffectFlags EffectFlags { get; init; } + + internal bool IsRedundant(LegacyControlPointProperties other) => + SliderVelocity == other.SliderVelocity && + TimingSignature == other.TimingSignature && + SampleBank == other.SampleBank && + CustomSampleBank == other.CustomSampleBank && + SampleVolume == other.SampleVolume && + EffectFlags == other.EffectFlags; + } } } From 384693a431757767d27594ef8906cd3d1ace0861 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 14:30:57 +0900 Subject: [PATCH 119/128] Fix test failure in `MultiplayerMatchSongSelect` due to multiple overlays present https://github.com/ppy/osu/actions/runs/4868337922/jobs/8681736995?pr=23308. --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index c0b6a0beab..9b130071cc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -98,6 +98,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) }); AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0); + + // A previous test's mod overlay could still be fading out. + AddUntilStep("wait for only one freemod overlay", () => this.ChildrenOfType().Count() == 1); + assertHasFreeModButton(allowedMod, false); assertHasFreeModButton(requiredMod, false); } From a3efae36907fc596061ffadf7e552d6c078c360a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 14:33:30 +0900 Subject: [PATCH 120/128] Fix potentially incorrect thread access in `OsuTabControlCheckbox` https://github.com/ppy/osu/actions/runs/4868337922/jobs/8681736829. --- .../UserInterface/OsuTabControlCheckbox.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index fa58ae27f2..71bc18737f 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -49,11 +49,10 @@ namespace osu.Game.Graphics.UserInterface private const float transition_length = 500; private Sample sampleChecked; private Sample sampleUnchecked; + private readonly SpriteIcon icon; public OsuTabControlCheckbox() { - SpriteIcon icon; - AutoSizeAxes = Axes.Both; Children = new Drawable[] @@ -85,14 +84,6 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.BottomLeft, } }; - - Current.ValueChanged += selected => - { - icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; - text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); - - updateFade(); - }; } [BackgroundDependencyLoader] @@ -103,6 +94,14 @@ namespace osu.Game.Graphics.UserInterface sampleChecked = audio.Samples.Get(@"UI/check-on"); sampleUnchecked = audio.Samples.Get(@"UI/check-off"); + + Current.ValueChanged += selected => + { + icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; + text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); + + updateFade(); + }; } protected override bool OnHover(HoverEvent e) From bede1292de37f4be3270c2e86c215783e3ef52d8 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 2 May 2023 22:24:07 -0700 Subject: [PATCH 121/128] Fix overlay scroll back button not absorbing hover from behind --- osu.Game/Overlays/OverlayScrollContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index 9752e04f44..ba7486a57f 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -185,6 +185,8 @@ namespace osu.Game.Overlays content.ScaleTo(1, 1000, Easing.OutElastic); base.OnMouseUp(e); } + + protected override bool OnHover(HoverEvent e) => true; } } } From 1c74f6e8ea60fe91335c95883bc8d434e51b07ac Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 2 May 2023 22:54:42 -0700 Subject: [PATCH 122/128] Fix regressed button hover fade in --- osu.Game/Overlays/OverlayScrollContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index ba7486a57f..2a615f0e12 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -186,7 +186,11 @@ namespace osu.Game.Overlays base.OnMouseUp(e); } - protected override bool OnHover(HoverEvent e) => true; + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + return true; + } } } } From 453143813fafc7a62736ab473a883a194d7e643d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 14:57:31 +0900 Subject: [PATCH 123/128] Extend input handling of osu! logo to full border area This is the easiest way to make this happen. It does mean the pink area is being drawn behind the white border, but I haven't found a scenario where this is noticeable. Closes #4217. --- osu.Game/Screens/Menu/OsuLogo.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 9430a1cda8..33dc23d88d 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -35,6 +35,12 @@ namespace osu.Game.Screens.Menu private const double transition_length = 300; + /// + /// The osu! logo sprite has a shadow included in its texture. + /// This adjustment vector is used to match the precise edge of the border of the logo. + /// + private static readonly Vector2 scale_adjust = new Vector2(0.96f); + private readonly Sprite logo; private readonly CircularContainer logoContainer; private readonly Container logoBounceContainer; @@ -150,7 +156,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = visualizer_default_alpha, - Size = new Vector2(0.96f) + Size = scale_adjust }, new Container { @@ -162,7 +168,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.88f), + Scale = scale_adjust, Masking = true, Children = new Drawable[] { From 5f781bd6de1ef4c2e067eec5e985722500b7b192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 May 2023 09:26:54 +0200 Subject: [PATCH 124/128] Move callback to `LoadComplete()` Is the more correct place for `BindValueChanged()` callbacks. --- osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 71bc18737f..8eb6b792c6 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -94,14 +94,19 @@ namespace osu.Game.Graphics.UserInterface sampleChecked = audio.Samples.Get(@"UI/check-on"); sampleUnchecked = audio.Samples.Get(@"UI/check-off"); + } - Current.ValueChanged += selected => + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(selected => { icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); updateFade(); - }; + }); } protected override bool OnHover(HoverEvent e) From de1b28bcb284e9e07b4b36eb9a3513a5f7228732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 May 2023 09:27:23 +0200 Subject: [PATCH 125/128] Fix incorrect initial state of checkbox This only ever barely used to work without the `(..., true)` in `master` because of haphazard operation ordering. --- osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 8eb6b792c6..c9e1f74917 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -106,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); updateFade(); - }); + }, true); } protected override bool OnHover(HoverEvent e) From 5757eb75299234da77876e70de818ae53d4e8728 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 18:20:12 +0900 Subject: [PATCH 126/128] Update a few more instances of `0.96f` scale constants --- osu.Game/Screens/Menu/IntroWelcome.cs | 2 +- osu.Game/Screens/Menu/OsuLogo.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index da44161507..2a6ebecb92 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = Color4.DarkBlue, - Size = new Vector2(0.96f) + Size = OsuLogo.SCALE_ADJUST, }, new Circle { diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 33dc23d88d..277b8bf888 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu /// The osu! logo sprite has a shadow included in its texture. /// This adjustment vector is used to match the precise edge of the border of the logo. /// - private static readonly Vector2 scale_adjust = new Vector2(0.96f); + public static readonly Vector2 SCALE_ADJUST = new Vector2(0.96f); private readonly Sprite logo; private readonly CircularContainer logoContainer; @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = visualizer_default_alpha, - Size = scale_adjust + Size = SCALE_ADJUST }, new Container { @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = scale_adjust, + Scale = SCALE_ADJUST, Masking = true, Children = new Drawable[] { @@ -412,7 +412,7 @@ namespace osu.Game.Screens.Menu public void Impact() { impactContainer.FadeOutFromOne(250, Easing.In); - impactContainer.ScaleTo(0.96f); + impactContainer.ScaleTo(SCALE_ADJUST); impactContainer.ScaleTo(1.12f, 250); } From cd31cff8cdd4c26c64183f640178b604e638682e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 18:41:29 +0900 Subject: [PATCH 127/128] Fix event subscriptions not being cleaned up in `DrawableCarouselBeatmap` The handling of cleanup is performed only the `Item_Set` method. This was already correctly called for `DrawableCarouselBeatmapSet`, but not for the class in question here. This would cause runaway memory usage at song select when opening many beatmaps to show their difficulties. For simplicity, we don't yet pool these (and generate the drawables each time a set is opened) which isn't great but likely will be improved upon when we update the visual / filtering of the carousel. But this simplicity caused the memory usage to blow out until exiting back to the main menu when cleanup would finally occur. --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index f08d14720b..55d442215b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -250,6 +250,9 @@ namespace osu.Game.Screens.Select.Carousel { base.Dispose(isDisposing); starDifficultyCancellationSource?.Cancel(); + + // This is important to clean up event subscriptions. + Item = null; } } } From 444f66b0eefdce3c4defaf46fffa927a886f6f51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 18:45:09 +0900 Subject: [PATCH 128/128] Move to base class for added safety --- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 3 --- .../Screens/Select/Carousel/DrawableCarouselItem.cs | 10 +++++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 55d442215b..f08d14720b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -250,9 +250,6 @@ namespace osu.Game.Screens.Select.Carousel { base.Dispose(isDisposing); starDifficultyCancellationSource?.Cancel(); - - // This is important to clean up event subscriptions. - Item = null; } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index f065926eb7..0c3de5848b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Select.Carousel item = value; - if (IsLoaded) + if (IsLoaded && !IsDisposed) UpdateItem(); } } @@ -165,5 +165,13 @@ namespace osu.Game.Screens.Select.Carousel Item.State.Value = CarouselItemState.Selected; return true; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + // This is important to clean up event subscriptions. + Item = null; + } } }