diff --git a/.gitignore b/.gitignore index 732b171f69..d122d25054 100644 --- a/.gitignore +++ b/.gitignore @@ -334,3 +334,5 @@ inspectcode # BenchmarkDotNet /BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index e34626a59e..47839608c9 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -4,5 +4,6 @@ M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals( M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. +M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900) T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 49d7d9249c..a452f93676 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -295,7 +295,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void assertControlPointPosition(int index, Vector2 position) => AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position.Value, 1)); - private Slider getSlider() => HitObjectContainer.Count > 0 ? (Slider)((DrawableSlider)HitObjectContainer[0]).HitObject : null; + private Slider getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 87da7ef417..6c077eb214 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Tests } hitObjectContainer.Add(drawableObject); - followPointRenderer.AddFollowPoints(drawableObject); + followPointRenderer.AddFollowPoints(objects[i]); } }); } @@ -180,10 +180,10 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("remove hitobject", () => { - var drawableObject = getFunc?.Invoke(); + var drawableObject = getFunc.Invoke(); hitObjectContainer.Remove(drawableObject); - followPointRenderer.RemoveFollowPoints(drawableObject); + followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); }); } @@ -215,10 +215,10 @@ namespace osu.Game.Rulesets.Osu.Tests DrawableOsuHitObject expectedStart = getObject(i); DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; - if (getGroup(i).Start != expectedStart) + if (getGroup(i).Start != expectedStart.HitObject) throw new AssertionException($"Object {i} expected to be the start of group {i}."); - if (getGroup(i).End != expectedEnd) + if (getGroup(i).End != expectedEnd?.HitObject) throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index c9e112f76d..c400e2f2ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -112,10 +112,10 @@ namespace osu.Game.Rulesets.Osu.Tests new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE }, }); - AddAssert("head samples updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle)); - AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples)); - AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples)); - AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0); + AddAssert("head samples updated", () => assertSamples(slider.HitObject.HeadCircle)); + AddAssert("tick samples not updated", () => slider.HitObject.NestedHitObjects.OfType().All(assertTickSamples)); + AddAssert("repeat samples updated", () => slider.HitObject.NestedHitObjects.OfType().All(assertSamples)); + AddAssert("tail has no samples", () => slider.HitObject.TailCircle.Samples.Count == 0); static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick"; @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests slider = (DrawableSlider)createSlider(repeats: 1); for (int i = 0; i < 2; i++) - ((Slider)slider.HitObject).NodeSamples.Add(new List { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } }); + slider.HitObject.NodeSamples.Add(new List { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } }); Add(slider); }); @@ -147,10 +147,10 @@ namespace osu.Game.Rulesets.Osu.Tests new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE }, }); - AddAssert("head samples not updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle)); - AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples)); - AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples)); - AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0); + AddAssert("head samples not updated", () => assertSamples(slider.HitObject.HeadCircle)); + AddAssert("tick samples not updated", () => slider.HitObject.NestedHitObjects.OfType().All(assertTickSamples)); + AddAssert("repeat samples not updated", () => slider.HitObject.NestedHitObjects.OfType().All(assertSamples)); + AddAssert("tail has no samples", () => slider.HitObject.TailCircle.Samples.Count == 0); static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick"; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index d3fb5defae..f851c7bfc9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override void OnSelected() { - AddInternal(ControlPointVisualiser = new PathControlPointVisualiser((Slider)slider.HitObject, true) + AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(slider.HitObject, true) { RemoveControlPointsRequested = removeControlPoints }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 2c41e6b0e9..3a9e19b361 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -31,19 +32,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// The which s will exit from. /// [NotNull] - public readonly DrawableOsuHitObject Start; + public readonly OsuHitObject Start; /// /// Creates a new . /// /// The which s will exit from. - public FollowPointConnection([NotNull] DrawableOsuHitObject start) + public FollowPointConnection([NotNull] OsuHitObject start) { Start = start; RelativeSizeAxes = Axes.Both; - StartTime.BindTo(Start.HitObject.StartTimeBindable); + StartTime.BindTo(start.StartTimeBindable); } protected override void LoadComplete() @@ -52,13 +53,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections bindEvents(Start); } - private DrawableOsuHitObject end; + private OsuHitObject end; /// /// The which s will enter. /// [CanBeNull] - public DrawableOsuHitObject End + public OsuHitObject End { get => end; set @@ -75,10 +76,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } } - private void bindEvents(DrawableOsuHitObject drawableObject) + private void bindEvents(OsuHitObject obj) { - drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh()); - drawableObject.HitObject.DefaultsApplied += _ => scheduleRefresh(); + obj.PositionBindable.BindValueChanged(_ => scheduleRefresh()); + obj.DefaultsApplied += _ => scheduleRefresh(); } private void scheduleRefresh() @@ -88,23 +89,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void refresh() { - OsuHitObject osuStart = Start.HitObject; - double startTime = osuStart.GetEndTime(); + double startTime = Start.GetEndTime(); LifetimeStart = startTime; - OsuHitObject osuEnd = End?.HitObject; - - if (osuEnd == null || osuEnd.NewCombo || osuStart is Spinner || osuEnd is Spinner) + if (End == null || End.NewCombo || Start is Spinner || End is Spinner) { // ensure we always set a lifetime for full LifetimeManagementContainer benefits LifetimeEnd = LifetimeStart; return; } - Vector2 startPosition = osuStart.StackedEndPosition; - Vector2 endPosition = osuEnd.StackedPosition; - double endTime = osuEnd.StartTime; + Vector2 startPosition = Start.StackedEndPosition; + Vector2 endPosition = End.StackedPosition; + double endTime = End.StartTime; Vector2 distanceVector = endPosition - startPosition; int distance = (int)distanceVector.Length; @@ -130,10 +128,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections AddInternal(fp = new FollowPoint()); + Debug.Assert(End != null); + fp.Position = pointStartPosition; fp.Rotation = rotation; fp.Alpha = 0; - fp.Scale = new Vector2(1.5f * osuEnd.Scale); + fp.Scale = new Vector2(1.5f * End.Scale); firstTransformStartTime ??= fadeInTime; @@ -141,12 +141,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections using (fp.BeginAbsoluteSequence(fadeInTime)) { - fp.FadeIn(osuEnd.TimeFadeIn); - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); + fp.FadeIn(End.TimeFadeIn); + fp.ScaleTo(End.Scale, End.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, End.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(End.TimeFadeIn); - finalTransformEndTime = fadeOutTime + osuEnd.TimeFadeIn; + finalTransformEndTime = fadeOutTime + End.TimeFadeIn; } point++; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 11571ea761..be1392d7c3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -24,19 +24,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public override bool RemoveCompletedTransforms => false; /// - /// Adds the s around a . + /// Adds the s around an . /// This includes s leading into , and s exiting . /// - /// The to add s for. - public void AddFollowPoints(DrawableOsuHitObject hitObject) + /// The to add s for. + public void AddFollowPoints(OsuHitObject hitObject) => addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); /// - /// Removes the s around a . + /// Removes the s around an . /// This includes s leading into , and s exiting . /// - /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject)); + /// The to remove s for. + public void RemoveFollowPoints(OsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject)); /// /// Adds a to this . diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index f56da65279..b0c4e3758d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -21,28 +20,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { - public ApproachCircle ApproachCircle { get; } - - private readonly IBindable positionBindable = new Bindable(); - private readonly IBindable stackHeightBindable = new Bindable(); - private readonly IBindable scaleBindable = new BindableFloat(); - public OsuAction? HitAction => HitArea.HitAction; - - public readonly HitReceptor HitArea; - public readonly SkinnableDrawable CirclePiece; - private readonly Container scaleContainer; - protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; + public ApproachCircle ApproachCircle { get; private set; } + public HitReceptor HitArea { get; private set; } + public SkinnableDrawable CirclePiece { get; private set; } + + private Container scaleContainer; private InputManager inputManager; public DrawableHitCircle(HitCircle h) : base(h) { - Origin = Anchor.Centre; + } - Position = HitObject.StackedPosition; + [BackgroundDependencyLoader] + private void load() + { + Origin = Anchor.Centre; InternalChildren = new Drawable[] { @@ -75,19 +71,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; Size = HitArea.DrawSize; - } - - [BackgroundDependencyLoader] - private void load() - { - positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); - - positionBindable.BindTo(HitObject.PositionBindable); - stackHeightBindable.BindTo(HitObject.StackHeightBindable); - scaleBindable.BindTo(HitObject.ScaleBindable); + PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition, true); + StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition, true); + ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 3730966fbb..208f79f165 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -2,18 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Osu.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuHitObject : DrawableHitObject { - private readonly ShakeContainer shakeContainer; + public readonly IBindable PositionBindable = new Bindable(); + public readonly IBindable StackHeightBindable = new Bindable(); + public readonly IBindable ScaleBindable = new BindableFloat(); + public readonly IBindable IndexInCurrentComboBindable = new Bindable(); // Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects. public override bool HandlePositionalInput => true; @@ -26,16 +32,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public Func CheckHittable; + private ShakeContainer shakeContainer; + protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) { + } + + [BackgroundDependencyLoader] + private void load() + { + Alpha = 0; + base.AddInternal(shakeContainer = new ShakeContainer { ShakeDuration = 30, RelativeSizeAxes = Axes.Both }); - Alpha = 0; + IndexInCurrentComboBindable.BindTo(HitObject.IndexInCurrentComboBindable); + PositionBindable.BindTo(HitObject.PositionBindable); + StackHeightBindable.BindTo(HitObject.StackHeightBindable); + ScaleBindable.BindTo(HitObject.ScaleBindable); } // Forward all internal management to shakeContainer. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 49535e7fff..98898ce1b4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -2,22 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Configuration; using osuTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Skinning; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuJudgement : DrawableJudgement { - protected SkinnableSprite Lighting; - - private Bindable lightingColour; + protected SkinnableLighting Lighting { get; private set; } [Resolved] private OsuConfigManager config { get; set; } @@ -34,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load() { - AddInternal(Lighting = new SkinnableSprite("lighting") + AddInternal(Lighting = new SkinnableLighting { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -59,19 +54,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.PrepareForUse(); - lightingColour?.UnbindAll(); - Lighting.ResetAnimation(); - - if (JudgedObject != null) - { - lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => Lighting.Colour = Result.IsHit ? colour.NewValue : Color4.Transparent, true); - } - else - { - Lighting.Colour = Color4.White; - } + Lighting.SetColourFrom(JudgedObject, Result); } private double fadeOutDelay; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index ddf3950689..d8dd0d7471 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -20,62 +20,54 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { + public new Slider HitObject => (Slider)base.HitObject; + public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child; - public readonly SliderBall Ball; - public readonly SkinnableDrawable Body; + public SliderBall Ball { get; private set; } + public SkinnableDrawable Body { get; private set; } public override bool DisplayResult => false; private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; - private readonly Container headContainer; - private readonly Container tailContainer; - private readonly Container tickContainer; - private readonly Container repeatContainer; + public readonly IBindable PathVersion = new Bindable(); - private readonly Slider slider; - - private readonly IBindable positionBindable = new Bindable(); - private readonly IBindable stackHeightBindable = new Bindable(); - private readonly IBindable scaleBindable = new BindableFloat(); + private Container headContainer; + private Container tailContainer; + private Container tickContainer; + private Container repeatContainer; public DrawableSlider(Slider s) : base(s) { - slider = s; - - Position = s.StackedPosition; + } + [BackgroundDependencyLoader] + private void load() + { InternalChildren = new Drawable[] { Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling), tailContainer = new Container { RelativeSizeAxes = Axes.Both }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, - Ball = new SliderBall(s, this) + Ball = new SliderBall(this) { GetInitialHitAction = () => HeadCircle.HitAction, BypassAutoSizeAxes = Axes.Both, - Scale = new Vector2(s.Scale), AlwaysPresent = true, Alpha = 0 }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, }; - } - [BackgroundDependencyLoader] - private void load() - { - positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - scaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue)); + PathVersion.BindTo(HitObject.Path.Version); - positionBindable.BindTo(HitObject.PositionBindable); - stackHeightBindable.BindTo(HitObject.StackHeightBindable); - scaleBindable.BindTo(HitObject.ScaleBindable); + PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition, true); + StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition, true); + ScaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue), true); AccentColour.BindValueChanged(colour => { @@ -162,20 +154,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (hitObject) { case SliderTailCircle tail: - return new DrawableSliderTail(slider, tail); + return new DrawableSliderTail(tail); case SliderHeadCircle head: - return new DrawableSliderHead(slider, head) + return new DrawableSliderHead(HitObject, head) { OnShake = Shake, CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true }; case SliderTick tick: - return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position }; + return new DrawableSliderTick(tick) { Position = tick.Position - HitObject.Position }; case SliderRepeat repeat: - return new DrawableSliderRepeat(repeat, this) { Position = repeat.Position - slider.Position }; + return new DrawableSliderRepeat(repeat, this) { Position = repeat.Position - HitObject.Position }; } return base.CreateNestedHitObject(hitObject); @@ -200,14 +192,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // keep the sliding sample playing at the current tracking position slidingSample.Balance.Value = CalculateSamplePlaybackBalance(Ball.X / OsuPlayfield.BASE_SIZE.X); - double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1); Ball.UpdateProgress(completionProgress); sliderBody?.UpdateProgress(completionProgress); foreach (DrawableHitObject hitObject in NestedHitObjects) { - if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody?.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody?.SnakedEnd ?? 0)); + if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(sliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(sliderBody?.SnakedEnd ?? 0)); if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking; } @@ -239,7 +231,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (userTriggered || Time.Current < slider.EndTime) + if (userTriggered || Time.Current < HitObject.EndTime) return; ApplyResult(r => r.Type = r.Judgement.MaxResult); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 04f563eeec..49ed9f12e3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -5,13 +5,11 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderHead : DrawableHitCircle { - private readonly IBindable positionBindable = new Bindable(); private readonly IBindable pathVersion = new Bindable(); protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; @@ -27,10 +25,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load() { - positionBindable.BindTo(HitObject.PositionBindable); pathVersion.BindTo(slider.Path.Version); - positionBindable.BindValueChanged(_ => updatePosition()); + PositionBindable.BindValueChanged(_ => updatePosition()); pathVersion.BindValueChanged(_ => updatePosition(), true); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 20159d18c8..9c382bd0a7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -22,9 +21,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - private readonly Drawable scaleContainer; - - public readonly Drawable CirclePiece; + public Drawable CirclePiece { get; private set; } + private Drawable scaleContainer; + private ReverseArrowPiece arrow; public override bool DisplayResult => false; @@ -33,10 +32,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { this.sliderRepeat = sliderRepeat; this.drawableSlider = drawableSlider; + } - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - + [BackgroundDependencyLoader] + private void load() + { Origin = Anchor.Centre; + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); InternalChild = scaleContainer = new Container { @@ -50,15 +52,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables arrow = new ReverseArrowPiece(), } }; - } - private readonly IBindable scaleBindable = new BindableFloat(); - - [BackgroundDependencyLoader] - private void load() - { - scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); - scaleBindable.BindTo(HitObject.ScaleBindable); + ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); } protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -100,8 +95,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private bool hasRotation; - private readonly ReverseArrowPiece arrow; - public void UpdateSnakingPosition(Vector2 start, Vector2 end) { // When the repeat is hit, the arrow should fade out on spot rather than following the slider diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 3acd3aefaf..3be5983c57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -3,7 +3,6 @@ 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; @@ -23,18 +22,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } - private readonly IBindable scaleBindable = new BindableFloat(); + private SkinnableDrawable circlePiece; + private Container scaleContainer; - private readonly SkinnableDrawable circlePiece; - - private readonly Container scaleContainer; - - public DrawableSliderTail(Slider slider, SliderTailCircle tailCircle) + public DrawableSliderTail(SliderTailCircle tailCircle) : base(tailCircle) { this.tailCircle = tailCircle; - Origin = Anchor.Centre; + } + [BackgroundDependencyLoader] + private void load() + { + Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); InternalChildren = new Drawable[] @@ -51,13 +51,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } }, }; - } - [BackgroundDependencyLoader] - private void load() - { - scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); - scaleBindable.BindTo(HitObject.ScaleBindable); + ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index b1450f61b1..2af51ea486 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -23,10 +22,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => false; - private readonly SkinnableDrawable scaleContainer; + private SkinnableDrawable scaleContainer; public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) + { + } + + [BackgroundDependencyLoader] + private void load() { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Origin = Anchor.Centre; @@ -49,15 +53,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, }; - } - private readonly IBindable scaleBindable = new BindableFloat(); - - [BackgroundDependencyLoader] - private void load() - { - scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); - scaleBindable.BindTo(HitObject.ScaleBindable); + ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index e841473cb1..77fbff9c51 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -16,34 +16,33 @@ using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinner : DrawableOsuHitObject { - protected readonly Spinner Spinner; + public new Spinner HitObject => (Spinner)base.HitObject; - private readonly Container ticks; + public SpinnerRotationTracker RotationTracker { get; private set; } + public SpinnerSpmCounter SpmCounter { get; private set; } - public readonly SpinnerRotationTracker RotationTracker; - public readonly SpinnerSpmCounter SpmCounter; - private readonly SpinnerBonusDisplay bonusDisplay; - - private readonly IBindable positionBindable = new Bindable(); + private Container ticks; + private SpinnerBonusDisplay bonusDisplay; + private Bindable isSpinning; private bool spinnerFrequencyModulate; public DrawableSpinner(Spinner s) : base(s) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { Origin = Anchor.Centre; - Position = s.Position; - RelativeSizeAxes = Axes.Both; - Spinner = s; - InternalChildren = new Drawable[] { ticks = new Container(), @@ -55,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinnerDisc()), - RotationTracker = new SpinnerRotationTracker(Spinner) + RotationTracker = new SpinnerRotationTracker(this) } }, SpmCounter = new SpinnerSpmCounter @@ -72,9 +71,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Y = -120, } }; - } - private Bindable isSpinning; + PositionBindable.BindValueChanged(pos => Position = pos.NewValue, true); + } protected override void LoadComplete() { @@ -172,13 +171,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return base.CreateNestedHitObject(hitObject); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - positionBindable.BindValueChanged(pos => Position = pos.NewValue); - positionBindable.BindTo(HitObject.PositionBindable); - } - protected override void ApplySkin(ISkinSource skin, bool allowFallback) { base.ApplySkin(skin, allowFallback); @@ -192,12 +184,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { get { - if (Spinner.SpinsRequired == 0) + if (HitObject.SpinsRequired == 0) // some spinners are so short they can't require an integer spin count. // these become implicitly hit. return 1; - return Math.Clamp(RotationTracker.RateAdjustedRotation / 360 / Spinner.SpinsRequired, 0, 1); + return Math.Clamp(RotationTracker.RateAdjustedRotation / 360 / HitObject.SpinsRequired, 0, 1); } } @@ -207,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RotationTracker.Complete.Value = Progress >= 1; - if (userTriggered || Time.Current < Spinner.EndTime) + if (userTriggered || Time.Current < HitObject.EndTime) return; // Trigger a miss result for remaining ticks to avoid infinite gameplay. @@ -222,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables r.Type = HitResult.Ok; else if (Progress > .75) r.Type = HitResult.Meh; - else if (Time.Current >= Spinner.EndTime) + else if (Time.Current >= HitObject.EndTime) r.Type = r.Judgement.MinResult; }); } @@ -274,7 +266,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { tick.TriggerResult(true); if (tick is DrawableSpinnerBonusTick) - bonusDisplay.SetBonusCount(spins - Spinner.SpinsRequired); + bonusDisplay.SetBonusCount(spins - HitObject.SpinsRequired); } wholeSpins++; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index e95cdc7ee3..c455c66e8d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Origin = Anchor.Centre, Texture = textures.Get(@"Gameplay/osu/disc"), }, - new TrianglesPiece((int)drawableHitObject.HitObject.StartTime) + new TrianglesPiece(drawableHitObject.GetHashCode()) { RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index e855317544..731852c221 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { private DrawableSpinner drawableSpinner; - private Spinner spinner; - private const float initial_scale = 1.3f; private const float idle_alpha = 0.2f; private const float tracking_alpha = 0.4f; @@ -52,7 +50,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private void load(OsuColour colours, DrawableHitObject drawableHitObject) { drawableSpinner = (DrawableSpinner)drawableHitObject; - spinner = (Spinner)drawableSpinner.HitObject; normalColour = colours.BlueDark; completeColour = colours.YellowLight; @@ -130,6 +127,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (!(drawableHitObject is DrawableSpinner)) return; + Spinner spinner = drawableSpinner.HitObject; + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) { this.ScaleTo(initial_scale); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs index cb3787a493..d2f4b71f19 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject) { - OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; + var drawableOsuObject = (DrawableOsuHitObject)drawableObject; state.BindTo(drawableObject.State); state.BindValueChanged(updateState, true); @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces circle.Colour = colour.NewValue; }, true); - indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable); + indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs index cedf2f6e09..29dff53f54 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs @@ -17,23 +17,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private IBindable pathVersion; private IBindable accentColour; - [Resolved] - private DrawableHitObject drawableObject { get; set; } - [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } - private Slider slider; - [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, DrawableHitObject drawableObject) { - slider = (Slider)drawableObject.HitObject; + var drawableSlider = (DrawableSlider)drawableObject; - scaleBindable = slider.ScaleBindable.GetBoundCopy(); + scaleBindable = drawableSlider.ScaleBindable.GetBoundCopy(); scaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true); - pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion = drawableSlider.PathVersion.GetBoundCopy(); pathVersion.BindValueChanged(_ => Refresh()); accentColour = drawableObject.AccentColour.GetBoundCopy(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 07dc6021c9..c5bf790377 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -30,15 +30,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces set => ball.Colour = value; } - private readonly Slider slider; private readonly Drawable followCircle; private readonly DrawableSlider drawableSlider; private readonly Drawable ball; - public SliderBall(Slider slider, DrawableSlider drawableSlider = null) + public SliderBall(DrawableSlider drawableSlider) { this.drawableSlider = drawableSlider; - this.slider = slider; Origin = Anchor.Centre; @@ -133,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (headCircleHitAction == null) timeToAcceptAnyKeyAfter = null; - var actions = drawableSlider?.OsuActionInputManager?.PressedActions; + var actions = drawableSlider.OsuActionInputManager?.PressedActions; // if the head circle was hit with a specific key, tracking should only occur while that key is pressed. if (headCircleHitAction != null && timeToAcceptAnyKeyAfter == null) @@ -147,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Tracking = // in valid time range - Time.Current >= slider.StartTime && Time.Current < slider.EndTime && + Time.Current >= drawableSlider.HitObject.StartTime && Time.Current < drawableSlider.HitObject.EndTime && // in valid position range lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action @@ -172,9 +170,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public void UpdateProgress(double completionProgress) { - var newPos = slider.CurvePositionAt(completionProgress); + var newPos = drawableSlider.HitObject.CurvePositionAt(completionProgress); - var diff = lastPosition.HasValue ? lastPosition.Value - newPos : newPos - slider.CurvePositionAt(completionProgress + 0.01f); + var diff = lastPosition.HasValue ? lastPosition.Value - newPos : newPos - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f); if (diff == Vector2.Zero) return; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index e24fa865ad..e63f25b7bc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -51,18 +51,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// private Vector2 snakedPathOffset; - private Slider slider; + private DrawableSlider drawableSlider; [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject) { - slider = (Slider)drawableObject.HitObject; + drawableSlider = (DrawableSlider)drawableObject; Refresh(); } public void UpdateProgress(double completionProgress) { + if (drawableSlider == null) + return; + + Slider slider = drawableSlider.HitObject; + var span = slider.SpanAt(completionProgress); var spanProgress = slider.ProgressAt(completionProgress); @@ -87,8 +92,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public void Refresh() { + if (drawableSlider == null) + return; + // Generate the entire curve - slider.Path.GetPathToProgress(CurrentCurve, 0, 1); + drawableSlider.HitObject.Path.GetPathToProgress(CurrentCurve, 0, 1); SetVertices(CurrentCurve); // Force the body to be the final path size to avoid excessive autosize computations @@ -132,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces SnakedStart = p0; SnakedEnd = p1; - slider.Path.GetPathToProgress(CurrentCurve, p0, p1); + drawableSlider.HitObject.Path.GetPathToProgress(CurrentCurve, p0, p1); SetVertices(CurrentCurve); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index 05ed38d241..910899c307 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SpinnerRotationTracker : CircularContainer { - private readonly Spinner spinner; - public override bool IsPresent => true; // handle input when hidden - public SpinnerRotationTracker(Spinner s) + private readonly DrawableSpinner drawableSpinner; + + public SpinnerRotationTracker(DrawableSpinner drawableSpinner) { - spinner = s; + this.drawableSpinner = drawableSpinner; RelativeSizeAxes = Axes.Both; } @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// /// Whether currently in the correct time range to allow spinning. /// - private bool isSpinnableTime => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; + private bool isSpinnableTime => drawableSpinner.HitObject.StartTime <= Time.Current && drawableSpinner.HitObject.EndTime > Time.Current; protected override bool OnMouseMove(MouseMoveEvent e) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs new file mode 100644 index 0000000000..02dc770285 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class SkinnableLighting : SkinnableSprite + { + private DrawableHitObject targetObject; + private JudgementResult targetResult; + + public SkinnableLighting() + : base("lighting") + { + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + updateColour(); + } + + /// + /// Updates the lighting colour from a given hitobject and result. + /// + /// The that's been judged. + /// The that was judged with. + public void SetColourFrom(DrawableHitObject targetObject, JudgementResult targetResult) + { + this.targetObject = targetObject; + this.targetResult = targetResult; + + updateColour(); + } + + private void updateColour() + { + if (targetObject == null || targetResult == null) + Colour = Color4.White; + else + Colour = targetResult.IsHit ? targetObject.AccentColour.Value : Color4.Transparent; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 382d6e53cc..418bf124ab 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject) { - OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; + var drawableOsuObject = (DrawableOsuHitObject)drawableObject; bool allowFallback = false; @@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Skinning state.BindTo(drawableObject.State); accentColour.BindTo(drawableObject.AccentColour); - indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable); + indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); Texture getTextureWithFallback(string name) { diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 56b5571ce1..b65e5a784c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -76,10 +76,10 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { - if (!(drawableHitObject is DrawableSpinner)) + if (!(drawableHitObject is DrawableSpinner d)) return; - var spinner = (Spinner)drawableSpinner.HitObject; + Spinner spinner = d.HitObject; using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) this.FadeOut(); diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 7b0d7acbbc..1954ff6e38 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; @@ -94,10 +95,10 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { - if (!(drawableHitObject is DrawableSpinner)) + if (!(drawableHitObject is DrawableSpinner d)) return; - var spinner = drawableSpinner.HitObject; + Spinner spinner = d.HitObject; using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) this.FadeOut(); diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs index 25ab96445a..836069013d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK.Graphics; @@ -26,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableHitObject drawableObject) + private void load(ISkinSource skin) { var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 50727d590a..321eeeab65 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -4,12 +4,15 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.Osu.Scoring; @@ -17,9 +20,6 @@ using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Rulesets.Osu.Configuration; using osuTK; namespace osu.Game.Rulesets.Osu.UI @@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Osu.UI public override void Add(DrawableHitObject h) { + DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; + h.OnNewResult += onNewResult; h.OnLoadComplete += d => { @@ -107,18 +109,19 @@ namespace osu.Game.Rulesets.Osu.UI base.Add(h); - DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; osuHitObject.CheckHittable = hitPolicy.IsHittable; - followPoints.AddFollowPoints(osuHitObject); + followPoints.AddFollowPoints(osuHitObject.HitObject); } public override bool Remove(DrawableHitObject h) { + DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; + bool result = base.Remove(h); if (result) - followPoints.RemoveFollowPoints((DrawableOsuHitObject)h); + followPoints.RemoveFollowPoints(osuHitObject.HitObject); return result; } diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs index 4a3dc58604..e4dc261363 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Audio private readonly ControlPointInfo controlPoints; private readonly Dictionary mappings = new Dictionary(); - private IBindableList samplePoints; + private readonly IBindableList samplePoints = new BindableList(); public DrumSampleContainer(ControlPointInfo controlPoints) { @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Audio [BackgroundDependencyLoader] private void load() { - samplePoints = controlPoints.SamplePoints.GetBoundCopy(); + samplePoints.BindTo(controlPoints.SamplePoints); samplePoints.BindCollectionChanged((_, __) => recreateMappings(), true); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 1d8231cce7..35473ee76c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private IBindableList users; + private readonly IBindableList users = new BindableList(); private TestReplayRecorder recorder; @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - users = streamingClient.PlayingUsers.GetBoundCopy(); + users.BindTo(streamingClient.PlayingUsers); users.BindCollectionChanged((obj, args) => { switch (args.Action) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 34444c2fa5..697ceacf0a 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private IBindableList playingUsers; + private readonly IBindableList playingUsers = new BindableList(); private FillFlowContainer userFlow; @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - playingUsers = spectatorStreaming.PlayingUsers.GetBoundCopy(); + playingUsers.BindTo(spectatorStreaming.PlayingUsers); playingUsers.BindCollectionChanged((sender, e) => Schedule(() => { switch (e.Action) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f77953a5c0..c7e2e0638c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -125,14 +125,14 @@ namespace osu.Game.Rulesets.Objects.Drawables Result = CreateResult(judgement); if (Result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - - LoadSamples(); } protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); + LoadSamples(); + HitObject.DefaultsApplied += onDefaultsApplied; startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 8c0e31c04c..ba3ac9113e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -14,13 +14,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public class ControlPointPart : TimelinePart { - private IBindableList controlPointGroups; + private readonly IBindableList controlPointGroups = new BindableList(); protected override void LoadBeatmap(WorkingBeatmap beatmap) { base.LoadBeatmap(beatmap); - controlPointGroups = beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups.BindTo(beatmap.Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { switch (args.Action) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index b9eb53b697..93fe6f9989 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public readonly ControlPointGroup Group; - private BindableList controlPoints; + private readonly IBindableList controlPoints = new BindableList(); [Resolved] private OsuColour colours { get; set; } @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { base.LoadComplete(); - controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy(); + controlPoints.BindTo(Group.ControlPoints); controlPoints.BindCollectionChanged((_, __) => { if (controlPoints.Count == 0) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 3f13e8e5d4..0da1b43201 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public class TimelineControlPointDisplay : TimelinePart { - private IBindableList controlPointGroups; + private readonly IBindableList controlPointGroups = new BindableList(); public TimelineControlPointDisplay() { @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadBeatmap(beatmap); - controlPointGroups = beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups.BindTo(beatmap.Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { switch (args.Action) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index e32616a574..fb69f16792 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public readonly ControlPointGroup Group; - private BindableList controlPoints; + private readonly IBindableList controlPoints = new BindableList(); [Resolved] private OsuColour colours { get; set; } @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy(); + controlPoints.BindTo(Group.ControlPoints); controlPoints.BindCollectionChanged((_, __) => { ClearInternal(); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 64f9526816..89d3c36250 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Edit.Timing private class ControlGroupAttributes : CompositeDrawable { - private readonly IBindableList controlPoints; + private readonly IBindableList controlPoints = new BindableList(); private readonly FillFlowContainer fill; @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Edit.Timing Spacing = new Vector2(2) }; - controlPoints = group.ControlPoints.GetBoundCopy(); + controlPoints.BindTo(group.ControlPoints); } [Resolved] diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f511382cde..09d861522a 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit.Timing private OsuButton deleteButton; private ControlPointTable table; - private IBindableList controlGroups; + private readonly IBindableList controlPointGroups = new BindableList(); [Resolved] private EditorClock clock { get; set; } @@ -124,11 +124,10 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); - controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); - - controlGroups.BindCollectionChanged((sender, args) => + controlPointGroups.BindTo(Beatmap.Value.Beatmap.ControlPointInfo.Groups); + controlPointGroups.BindCollectionChanged((sender, args) => { - table.ControlGroups = controlGroups; + table.ControlGroups = controlPointGroups; changeHandler.SaveState(); }, true); } diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index d340f67575..4f09aec0b6 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Screens.Play; @@ -25,6 +26,8 @@ namespace osu.Game.Skinning private readonly IBindable samplePlaybackDisabled = new Bindable(); + private ScheduledDelegate scheduledStart; + [BackgroundDependencyLoader(true)] private void load(ISamplePlaybackDisabler samplePlaybackDisabler) { @@ -39,12 +42,14 @@ namespace osu.Game.Skinning // let non-looping samples that have already been started play out to completion (sounds better than abruptly cutting off). if (!Looping) return; + cancelPendingStart(); + if (disabled.NewValue) base.Stop(); else { // schedule so we don't start playing a sample which is no longer alive. - Schedule(() => + scheduledStart = Schedule(() => { if (RequestedPlaying) base.Play(); @@ -56,6 +61,7 @@ namespace osu.Game.Skinning public override void Play() { + cancelPendingStart(); RequestedPlaying = true; if (samplePlaybackDisabled.Value) @@ -66,8 +72,15 @@ namespace osu.Game.Skinning public override void Stop() { + cancelPendingStart(); RequestedPlaying = false; base.Stop(); } + + private void cancelPendingStart() + { + scheduledStart?.Cancel(); + scheduledStart = null; + } } } diff --git a/osu.iOS.props b/osu.iOS.props index 76c496cd2d..40ecfffcca 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,6 +73,14 @@ + + + $(NoWarn);NU1605 + + + + +