diff --git a/osu.Android.props b/osu.Android.props index e3285222f8..bbe8426316 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs new file mode 100644 index 0000000000..8b3fead366 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneHitCircleApplication : OsuTestScene + { + [Test] + public void TestApplyNewCircle() + { + DrawableHitCircle dho = null; + + AddStep("create circle", () => Child = dho = new DrawableHitCircle(prepareObject(new HitCircle + { + Position = new Vector2(256, 192), + IndexInCurrentCombo = 0 + })) + { + Clock = new FramedClock(new StopwatchClock()) + }); + + AddStep("apply new circle", () => dho.Apply(prepareObject(new HitCircle + { + Position = new Vector2(128, 128), + ComboIndex = 1, + }))); + } + + private HitCircle prepareObject(HitCircle circle) + { + circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + return circle; + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs new file mode 100644 index 0000000000..f76c7e2a3e --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -0,0 +1,59 @@ +// 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.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneSliderApplication : OsuTestScene + { + [Test] + public void TestApplyNewSlider() + { + DrawableSlider dho = null; + + AddStep("create slider", () => Child = dho = new DrawableSlider(prepareObject(new Slider + { + Position = new Vector2(256, 192), + IndexInCurrentCombo = 0, + StartTime = Time.Current, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(150, 100), + new Vector2(300, 0), + }) + }))); + + AddWaitStep("wait for progression", 1); + + AddStep("apply new slider", () => dho.Apply(prepareObject(new Slider + { + Position = new Vector2(256, 192), + ComboIndex = 1, + StartTime = dho.HitObject.StartTime, + Path = new SliderPath(PathType.Bezier, new[] + { + Vector2.Zero, + new Vector2(150, 100), + new Vector2(300, 0), + }), + RepeatCount = 1 + }))); + } + + private Slider prepareObject(Slider slider) + { + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + return slider; + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs new file mode 100644 index 0000000000..5951574079 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneSpinnerApplication : OsuTestScene + { + [Test] + public void TestApplyNewCircle() + { + DrawableSpinner dho = null; + + AddStep("create spinner", () => Child = dho = new DrawableSpinner(prepareObject(new Spinner + { + Position = new Vector2(256, 192), + IndexInCurrentCombo = 0, + Duration = 0, + })) + { + Clock = new FramedClock(new StopwatchClock()) + }); + + AddStep("apply new spinner", () => dho.Apply(prepareObject(new Spinner + { + Position = new Vector2(256, 192), + ComboIndex = 1, + Duration = 1000, + }))); + } + + private Spinner prepareObject(Spinner circle) + { + circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + return circle; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index b0c4e3758d..77d24db084 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container scaleContainer; private InputManager inputManager; - public DrawableHitCircle(HitCircle h) + public DrawableHitCircle([CanBeNull] HitCircle h = null) : base(h) { } @@ -72,10 +73,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = HitArea.DrawSize; - 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); + PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); + AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 208f79f165..d17bf93fa0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.UI; using osuTK; @@ -49,6 +50,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ShakeDuration = 30, RelativeSizeAxes = Axes.Both }); + } + + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); IndexInCurrentComboBindable.BindTo(HitObject.IndexInCurrentComboBindable); PositionBindable.BindTo(HitObject.PositionBindable); @@ -56,6 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindTo(HitObject.ScaleBindable); } + protected override void OnFree(HitObject hitObject) + { + base.OnFree(hitObject); + + IndexInCurrentComboBindable.UnbindFrom(HitObject.IndexInCurrentComboBindable); + PositionBindable.UnbindFrom(HitObject.PositionBindable); + StackHeightBindable.UnbindFrom(HitObject.StackHeightBindable); + ScaleBindable.UnbindFrom(HitObject.ScaleBindable); + } + // Forward all internal management to shakeContainer. // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index d8dd0d7471..3f91a31066 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osuTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -32,14 +33,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; - public readonly IBindable PathVersion = new Bindable(); + public IBindable PathVersion => pathVersion; + private readonly Bindable pathVersion = new Bindable(); private Container headContainer; private Container tailContainer; private Container tickContainer; private Container repeatContainer; - public DrawableSlider(Slider s) + public DrawableSlider([CanBeNull] Slider s = null) : base(s) { } @@ -63,11 +65,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables headContainer = new Container { RelativeSizeAxes = Axes.Both }, }; - PathVersion.BindTo(HitObject.Path.Version); - - PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition, true); - StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition, true); - ScaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue), true); + PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + ScaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue)); AccentColour.BindValueChanged(colour => { @@ -78,6 +78,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.BindValueChanged(updateSlidingSample); } + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); + + // Ensure that the version will change after the upcoming BindTo(). + pathVersion.Value = int.MaxValue; + PathVersion.BindTo(HitObject.Path.Version); + } + + protected override void OnFree(HitObject hitObject) + { + base.OnFree(hitObject); + + PathVersion.UnbindFrom(HitObject.Path.Version); + } + private PausableSkinnableSound slidingSample; protected override void LoadSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 77fbff9c51..eb125969b0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Bindable isSpinning; private bool spinnerFrequencyModulate; - public DrawableSpinner(Spinner s) + public DrawableSpinner([CanBeNull] Spinner s = null) : base(s) { } @@ -72,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } }; - PositionBindable.BindValueChanged(pos => Position = pos.NewValue, true); + PositionBindable.BindValueChanged(pos => Position = pos.NewValue); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs index 4eeb4a1475..fb0917341e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; @@ -28,9 +27,10 @@ namespace osu.Game.Rulesets.Taiko.Tests // suppress locally to allow hiding the visuals wherever necessary. } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + Result.Type = Type; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5c3f57c2d0..7a4e136553 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -27,7 +28,10 @@ namespace osu.Game.Rulesets.Objects.Drawables { public event Action DefaultsApplied; - public readonly HitObject HitObject; + /// + /// The currently represented by this . + /// + public HitObject HitObject { get; private set; } /// /// The colour used for various elements of this DrawableHitObject. @@ -96,10 +100,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected virtual float SamplePlaybackPosition => 0.5f; - private BindableList samplesBindable; - private Bindable startTimeBindable; - private Bindable userPositionalHitSounds; - private Bindable comboIndexBindable; + private readonly Bindable startTimeBindable = new Bindable(); + private readonly BindableList samplesBindable = new BindableList(); + private readonly Bindable userPositionalHitSounds = new Bindable(); + private readonly Bindable comboIndexBindable = new Bindable(); public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; @@ -111,52 +115,157 @@ namespace osu.Game.Rulesets.Objects.Drawables public IBindable State => state; - protected DrawableHitObject([NotNull] HitObject hitObject) + /// + /// Whether is currently applied. + /// + private bool hasHitObjectApplied; + + /// + /// Creates a new . + /// + /// + /// The to be initially applied to this . + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// + protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { - HitObject = hitObject ?? throw new ArgumentNullException(nameof(hitObject)); + HitObject = initialHitObject; } [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - userPositionalHitSounds = config.GetBindable(OsuSetting.PositionalHitSounds); - var judgement = HitObject.CreateJudgement(); - - Result = CreateResult(judgement); - if (Result == null) - throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); } protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); - LoadSamples(); - - HitObject.DefaultsApplied += onDefaultsApplied; - - startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); - startTimeBindable.BindValueChanged(_ => updateState(State.Value, true)); - - if (HitObject is IHasComboInformation combo) - { - comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy(); - comboIndexBindable.BindValueChanged(_ => updateComboColour(), true); - } - - samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); - samplesBindable.CollectionChanged += (_, __) => LoadSamples(); - - apply(HitObject); + if (HitObject != null) + Apply(HitObject); } protected override void LoadComplete() { base.LoadComplete(); + startTimeBindable.BindValueChanged(_ => updateState(State.Value, true)); + comboIndexBindable.BindValueChanged(_ => updateComboColour(), true); + updateState(ArmedState.Idle, true); } + /// + /// Applies a new to be represented by this . + /// + /// The to apply. + public void Apply(HitObject hitObject) + { + free(); + + HitObject = hitObject ?? throw new InvalidOperationException($"Cannot apply a null {nameof(HitObject)}."); + + // Ensure this DHO has a result. + Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + + foreach (var h in HitObject.NestedHitObjects) + { + var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); + + drawableNested.OnNewResult += onNewResult; + drawableNested.OnRevertResult += onRevertResult; + drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState; + + nestedHitObjects.Value.Add(drawableNested); + AddNestedHitObject(drawableNested); + } + + startTimeBindable.BindTo(HitObject.StartTimeBindable); + if (HitObject is IHasComboInformation combo) + comboIndexBindable.BindTo(combo.ComboIndexBindable); + + samplesBindable.BindTo(HitObject.SamplesBindable); + samplesBindable.BindCollectionChanged(onSamplesChanged, true); + + HitObject.DefaultsApplied += onDefaultsApplied; + + OnApply(hitObject); + + // If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates. + if (IsLoaded) + Schedule(() => updateState(ArmedState.Idle, true)); + + hasHitObjectApplied = true; + } + + /// + /// Removes the currently applied + /// + private void free() + { + if (!hasHitObjectApplied) + return; + + startTimeBindable.UnbindFrom(HitObject.StartTimeBindable); + if (HitObject is IHasComboInformation combo) + comboIndexBindable.UnbindFrom(combo.ComboIndexBindable); + + samplesBindable.UnbindFrom(HitObject.SamplesBindable); + + // When a new hitobject is applied, the samples will be cleared before re-populating. + // In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply(). + samplesBindable.CollectionChanged -= onSamplesChanged; + + if (nestedHitObjects.IsValueCreated) + { + foreach (var obj in nestedHitObjects.Value) + { + obj.OnNewResult -= onNewResult; + obj.OnRevertResult -= onRevertResult; + obj.ApplyCustomUpdateState -= onApplyCustomUpdateState; + } + + nestedHitObjects.Value.Clear(); + ClearNestedHitObjects(); + } + + HitObject.DefaultsApplied -= onDefaultsApplied; + + OnFree(HitObject); + + HitObject = null; + hasHitObjectApplied = false; + } + + protected sealed override void FreeAfterUse() + { + base.FreeAfterUse(); + + // Freeing while not in a pool would cause the DHO to not be usable elsewhere in the hierarchy without being re-applied. + if (!IsInPool) + return; + + free(); + } + + /// + /// Invoked for this to take on any values from a newly-applied . + /// + /// The being applied. + protected virtual void OnApply(HitObject hitObject) + { + } + + /// + /// Invoked for this to revert any values previously taken on from the currently-applied . + /// + /// The currently-applied . + protected virtual void OnFree(HitObject hitObject) + { + } + /// /// Invoked by the base to populate samples, once on initial load and potentially again on any change to the samples collection. /// @@ -183,34 +292,20 @@ namespace osu.Game.Rulesets.Objects.Drawables AddInternal(Samples); } + private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples(); + + private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result); + + private void onRevertResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnRevertResult?.Invoke(drawableHitObject, result); + + private void onApplyCustomUpdateState(DrawableHitObject drawableHitObject, ArmedState state) => ApplyCustomUpdateState?.Invoke(drawableHitObject, state); + private void onDefaultsApplied(HitObject hitObject) { - apply(hitObject); - updateState(state.Value, true); + Apply(hitObject); DefaultsApplied?.Invoke(this); } - private void apply(HitObject hitObject) - { - if (nestedHitObjects.IsValueCreated) - { - nestedHitObjects.Value.Clear(); - ClearNestedHitObjects(); - } - - foreach (var h in hitObject.NestedHitObjects) - { - var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); - - drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); - drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); - drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - - nestedHitObjects.Value.Add(drawableNested); - AddNestedHitObject(drawableNested); - } - } - /// /// Invoked by the base to add nested s to the hierarchy. /// @@ -600,19 +695,20 @@ namespace osu.Game.Rulesets.Objects.Drawables protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - HitObject.DefaultsApplied -= onDefaultsApplied; + + if (HitObject != null) + HitObject.DefaultsApplied -= onDefaultsApplied; } } public abstract class DrawableHitObject : DrawableHitObject where TObject : HitObject { - public new readonly TObject HitObject; + public new TObject HitObject => (TObject)base.HitObject; protected DrawableHitObject(TObject hitObject) : base(hitObject) { - HitObject = hitObject; } } } diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 4a1aaa62bf..cc9cbf7b59 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -3,14 +3,14 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; namespace osu.Game.Skinning { /// /// A drawable which has a callback when the skin changes. /// - public abstract class SkinReloadableDrawable : CompositeDrawable + public abstract class SkinReloadableDrawable : PoolableDrawable { /// /// Invoked when has changed. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 832722c729..8f0cc58594 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ad6dd2a0b5..f766e0ec03 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - +