diff --git a/osu.Android.props b/osu.Android.props
index dc3e14c141..afc5d4ec52 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index dbab048b25..53bf1ea566 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Timing;
@@ -194,13 +193,7 @@ namespace osu.Game.Rulesets.Osu.Tests
addSeekStep(0);
- AddStep("adjust track rate", () => MusicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate)));
- // autoplay replay frames use track time;
- // if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time.
- // therefore we need to apply the rate adjustment to the replay itself to change from track time to real time,
- // as real time is what we care about for spinners
- // (so we're making the spin take 1000ms in real time *always*, regardless of the track clock's rate).
- transformReplay(replay => applyRateAdjustment(replay, rate));
+ AddStep("adjust track rate", () => Player.GameplayClockContainer.UserPlaybackRate.Value = rate);
addSeekStep(1000);
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 07f40f763b..b73ae5eeb9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Tracking.BindValueChanged(updateSlidingSample);
}
- private SkinnableSound slidingSample;
+ private PausableSkinnableSound slidingSample;
protected override void LoadSamples()
{
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
clone.Name = "sliderslide";
- AddInternal(slidingSample = new SkinnableSound(clone)
+ AddInternal(slidingSample = new PausableSkinnableSound(clone)
{
Looping = true
});
@@ -112,10 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private void updateSlidingSample(ValueChangedEvent tracking)
{
- // note that samples will not start playing if exiting a seek operation in the middle of a slider.
- // may be something we want to address at a later point, but not so easy to make happen right now
- // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update).
- if (tracking.NewValue && ShouldPlaySamples)
+ if (tracking.NewValue)
slidingSample?.Play();
else
slidingSample?.Stop();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index a57bb466c7..6488c60acf 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
isSpinning.BindValueChanged(updateSpinningSample);
}
- private SkinnableSound spinningSample;
+ private PausableSkinnableSound spinningSample;
private const float spinning_sample_initial_frequency = 1.0f;
private const float spinning_sample_modulated_base_frequency = 0.5f;
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
clone.Name = "spinnerspin";
- AddInternal(spinningSample = new SkinnableSound(clone)
+ AddInternal(spinningSample = new PausableSkinnableSound(clone)
{
Volume = { Value = 0 },
Looping = true,
@@ -113,10 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private void updateSpinningSample(ValueChangedEvent tracking)
{
- // note that samples will not start playing if exiting a seek operation in the middle of a spinner.
- // may be something we want to address at a later point, but not so easy to make happen right now
- // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update).
- if (tracking.NewValue && ShouldPlaySamples)
+ if (tracking.NewValue)
{
spinningSample?.Play();
spinningSample?.VolumeTo(1, 200);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs
index f1a782cbb5..05ed38d241 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs
@@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
+using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@@ -77,6 +79,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private bool rotationTransferred;
+ [Resolved(canBeNull: true)]
+ private GameplayClock gameplayClock { get; set; }
+
protected override void Update()
{
base.Update();
@@ -126,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
currentRotation += angle;
// rate has to be applied each frame, because it's not guaranteed to be constant throughout playback
// (see: ModTimeRamp)
- RateAdjustedRotation += (float)(Math.Abs(angle) * Clock.Rate);
+ RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate));
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs
index 7c39c040b1..fcf7c529f5 100644
--- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs
+++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs
@@ -42,9 +42,9 @@ namespace osu.Game.Rulesets.Taiko.Audio
}
}
- private SkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd)
+ private PausableSkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd)
{
- var drawable = new SkinnableSound(hitSampleInfo)
+ var drawable = new PausableSkinnableSound(hitSampleInfo)
{
LifetimeStart = lifetimeStart,
LifetimeEnd = lifetimeEnd
@@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Taiko.Audio
public class DrumSample
{
- public SkinnableSound Centre;
- public SkinnableSound Rim;
+ public PausableSkinnableSound Centre;
+ public PausableSkinnableSound Rim;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs
index b5ec2e8def..19493271be 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs
@@ -1,21 +1,64 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Skinning
{
public class LegacyHitExplosion : CompositeDrawable
{
- public LegacyHitExplosion(Drawable sprite)
- {
- InternalChild = sprite;
+ private readonly Drawable sprite;
+ private readonly Drawable strongSprite;
+ private DrawableStrongNestedHit nestedStrongHit;
+ private bool switchedToStrongSprite;
+
+ ///
+ /// Creates a new legacy hit explosion.
+ ///
+ ///
+ /// Contrary to stable's, this implementation doesn't require a frame-perfect hit
+ /// for the strong sprite to be displayed.
+ ///
+ /// The normal legacy explosion sprite.
+ /// The strong legacy explosion sprite.
+ public LegacyHitExplosion(Drawable sprite, Drawable strongSprite = null)
+ {
+ this.sprite = sprite;
+ this.strongSprite = strongSprite;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject judgedObject)
+ {
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
+
+ AddInternal(sprite.With(s =>
+ {
+ s.Anchor = Anchor.Centre;
+ s.Origin = Anchor.Centre;
+ }));
+
+ if (strongSprite != null)
+ {
+ AddInternal(strongSprite.With(s =>
+ {
+ s.Alpha = 0;
+ s.Anchor = Anchor.Centre;
+ s.Origin = Anchor.Centre;
+ }));
+ }
+
+ if (judgedObject is DrawableHit hit)
+ nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit;
}
protected override void LoadComplete()
@@ -33,5 +76,25 @@ namespace osu.Game.Rulesets.Taiko.Skinning
Expire(true);
}
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (shouldSwitchToStrongSprite() && !switchedToStrongSprite)
+ {
+ sprite.FadeOut(50, Easing.OutQuint);
+ strongSprite.FadeIn(50, Easing.OutQuint);
+ switchedToStrongSprite = true;
+ }
+ }
+
+ private bool shouldSwitchToStrongSprite()
+ {
+ if (nestedStrongHit == null || strongSprite == null)
+ return false;
+
+ return nestedStrongHit.IsHit;
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
index c222ccb51f..d320b824e6 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -74,15 +74,23 @@ namespace osu.Game.Rulesets.Taiko.Skinning
return null;
- case TaikoSkinComponents.TaikoExplosionGood:
- case TaikoSkinComponents.TaikoExplosionGoodStrong:
- case TaikoSkinComponents.TaikoExplosionGreat:
- case TaikoSkinComponents.TaikoExplosionGreatStrong:
case TaikoSkinComponents.TaikoExplosionMiss:
- var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
- if (sprite != null)
- return new LegacyHitExplosion(sprite);
+ var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
+ if (missSprite != null)
+ return new LegacyHitExplosion(missSprite);
+
+ return null;
+
+ case TaikoSkinComponents.TaikoExplosionGood:
+ case TaikoSkinComponents.TaikoExplosionGreat:
+
+ var hitName = getHitName(taikoComponent.Component);
+ var hitSprite = this.GetAnimation(hitName, true, false);
+ var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
+
+ if (hitSprite != null)
+ return new LegacyHitExplosion(hitSprite, strongHitSprite);
return null;
@@ -109,17 +117,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning
case TaikoSkinComponents.TaikoExplosionGood:
return "taiko-hit100";
- case TaikoSkinComponents.TaikoExplosionGoodStrong:
- return "taiko-hit100k";
-
case TaikoSkinComponents.TaikoExplosionGreat:
return "taiko-hit300";
-
- case TaikoSkinComponents.TaikoExplosionGreatStrong:
- return "taiko-hit300k";
}
- throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type");
+ throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}");
}
public override SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
index 0d785adb4a..ac4fb51661 100644
--- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
@@ -17,9 +17,7 @@ namespace osu.Game.Rulesets.Taiko
BarLine,
TaikoExplosionMiss,
TaikoExplosionGood,
- TaikoExplosionGoodStrong,
TaikoExplosionGreat,
- TaikoExplosionGreatStrong,
Scroller,
Mascot,
}
diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
index efd1b25046..53765f04dd 100644
--- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
using osuTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -10,7 +9,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.UI
@@ -50,10 +48,10 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load()
{
- Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion(JudgedObject, result));
+ Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(JudgedObject, result));
}
- private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject)
+ private static TaikoSkinComponents getComponentName(HitResult result)
{
switch (result)
{
@@ -61,28 +59,13 @@ namespace osu.Game.Rulesets.Taiko.UI
return TaikoSkinComponents.TaikoExplosionMiss;
case HitResult.Good:
- return useStrongExplosion(judgedObject)
- ? TaikoSkinComponents.TaikoExplosionGoodStrong
- : TaikoSkinComponents.TaikoExplosionGood;
+ return TaikoSkinComponents.TaikoExplosionGood;
case HitResult.Great:
- return useStrongExplosion(judgedObject)
- ? TaikoSkinComponents.TaikoExplosionGreatStrong
- : TaikoSkinComponents.TaikoExplosionGreat;
+ return TaikoSkinComponents.TaikoExplosionGreat;
}
- throw new ArgumentOutOfRangeException(nameof(judgedObject), "Invalid result type");
- }
-
- private bool useStrongExplosion(DrawableHitObject judgedObject)
- {
- if (!(judgedObject.HitObject is Hit))
- return false;
-
- if (!(judgedObject.NestedHitObjects.SingleOrDefault() is DrawableStrongNestedHit nestedHit))
- return false;
-
- return judgedObject.Result.Type == nestedHit.Result.Type;
+ throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}");
}
///
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 7b3fbb1faf..120cf264c3 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -215,16 +215,12 @@ namespace osu.Game.Rulesets.Taiko.UI
private void addDrumRollHit(DrawableDrumRollTick drawableTick) =>
drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick));
- ///
- /// As legacy skins have different explosions for singular and double strong hits,
- /// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame.
- ///
- private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) => Schedule(() =>
+ private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type)
{
hitExplosionContainer.Add(new HitExplosion(drawableObject, result));
if (drawableObject.HitObject.Kiai)
kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type));
- });
+ }
private class ProxyContainer : LifetimeManagementContainer
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index a690eb3b59..d46769a7c0 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -4,10 +4,12 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics.Audio;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Game.Audio;
@@ -106,9 +108,14 @@ namespace osu.Game.Tests.Gameplay
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
SelectedMods.Value = new[] { testedMod };
- Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0));
+ var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
- gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
+ Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)
+ {
+ Child = beatmapSkinSourceContainer
+ });
+
+ beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
{
Clock = gameplayContainer.GameplayClock
});
@@ -116,7 +123,7 @@ namespace osu.Game.Tests.Gameplay
AddStep("start", () => gameplayContainer.Start());
- AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate);
+ AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType().First().AggregateFrequency.Value == expectedRate);
}
private class TestSkin : LegacySkin
@@ -168,8 +175,6 @@ namespace osu.Game.Tests.Gameplay
: base(sampleInfo)
{
}
-
- public new SampleChannel Channel => base.Channel;
}
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
index 09f5ac2224..b82e776164 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
@@ -4,7 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
@@ -17,16 +17,26 @@ namespace osu.Game.Tests.Visual.Editing
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap;
+ protected override bool ScrollUsingMouseWheel => false;
+
public TestSceneTimingScreen()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
}
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
+ Beatmap.Disabled = true;
+
Child = new TimingScreen();
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ Beatmap.Disabled = false;
+ base.Dispose(isDisposing);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 420bf29429..ac0e8eb0d4 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestQuickRetryFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
- AddStep("quick retry", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke());
+ AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke());
confirmExited();
}
@@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestQuickExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
- AddStep("quick exit", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke());
+ AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke());
confirmExited();
}
@@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestQuickExitFromGameplay()
{
- AddStep("quick exit", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke());
+ AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke());
confirmExited();
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
index ed75d83151..8f2011e5dd 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
@@ -22,11 +22,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableSound : OsuTestScene
{
- [Cached]
+ [Cached(typeof(ISamplePlaybackDisabler))]
private GameplayClock gameplayClock = new GameplayClock(new FramedClock());
private TestSkinSourceContainer skinSource;
- private SkinnableSound skinnableSound;
+ private PausableSkinnableSound skinnableSound;
[SetUp]
public void SetUp() => Schedule(() =>
@@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
Clock = gameplayClock,
RelativeSizeAxes = Axes.Both,
- Child = skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide"))
+ Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide"))
},
};
});
diff --git a/osu.Game/Extensions/EditorDisplayExtensions.cs b/osu.Game/Extensions/EditorDisplayExtensions.cs
new file mode 100644
index 0000000000..f749b88b46
--- /dev/null
+++ b/osu.Game/Extensions/EditorDisplayExtensions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Extensions
+{
+ public static class EditorDisplayExtensions
+ {
+ ///
+ /// Get an editor formatted string (mm:ss:mss)
+ ///
+ /// A time value in milliseconds.
+ /// An editor formatted display string.
+ public static string ToEditorFormattedString(this double milliseconds) =>
+ ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds));
+
+ ///
+ /// Get an editor formatted string (mm:ss:mss)
+ ///
+ /// A time value.
+ /// An editor formatted display string.
+ public static string ToEditorFormattedString(this TimeSpan timeSpan) =>
+ $"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{timeSpan:mm\\:ss\\:fff}";
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs
index 559d127cfc..50a6d501b6 100644
--- a/osu.Game/Rulesets/Mods/IApplicableToSample.cs
+++ b/osu.Game/Rulesets/Mods/IApplicableToSample.cs
@@ -1,7 +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 osu.Framework.Audio.Sample;
+using osu.Framework.Graphics.Audio;
namespace osu.Game.Rulesets.Mods
{
@@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods
///
public interface IApplicableToSample : IApplicableMod
{
- void ApplyToSample(SampleChannel sample);
+ void ApplyToSample(DrawableSample sample);
}
}
diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs
index 4004953cd1..282de3a8e1 100644
--- a/osu.Game/Rulesets/Mods/ModNightcore.cs
+++ b/osu.Game/Rulesets/Mods/ModNightcore.cs
@@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mods
public class NightcoreBeatContainer : BeatSyncedContainer
{
- private SkinnableSound hatSample;
- private SkinnableSound clapSample;
- private SkinnableSound kickSample;
- private SkinnableSound finishSample;
+ private PausableSkinnableSound hatSample;
+ private PausableSkinnableSound clapSample;
+ private PausableSkinnableSound kickSample;
+ private PausableSkinnableSound finishSample;
private int? firstBeat;
@@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Mods
{
InternalChildren = new Drawable[]
{
- hatSample = new SkinnableSound(new SampleInfo("nightcore-hat")),
- clapSample = new SkinnableSound(new SampleInfo("nightcore-clap")),
- kickSample = new SkinnableSound(new SampleInfo("nightcore-kick")),
- finishSample = new SkinnableSound(new SampleInfo("nightcore-finish")),
+ hatSample = new PausableSkinnableSound(new SampleInfo("nightcore-hat")),
+ clapSample = new PausableSkinnableSound(new SampleInfo("nightcore-clap")),
+ kickSample = new PausableSkinnableSound(new SampleInfo("nightcore-kick")),
+ finishSample = new PausableSkinnableSound(new SampleInfo("nightcore-finish")),
};
}
diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
index fec21764b0..2150b0fb68 100644
--- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
@@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
+using osu.Framework.Graphics.Audio;
namespace osu.Game.Rulesets.Mods
{
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
}
- public virtual void ApplyToSample(SampleChannel sample)
+ public virtual void ApplyToSample(DrawableSample sample)
{
sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index 20c8d0f3e7..4d43ae73d3 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -6,11 +6,11 @@ using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
+using osu.Framework.Graphics.Audio;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
-using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Objects;
-using osu.Framework.Audio.Sample;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mods
{
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mods
AdjustPitch.TriggerChange();
}
- public void ApplyToSample(SampleChannel sample)
+ public void ApplyToSample(DrawableSample sample)
{
sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index a48627208d..891008801e 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -18,7 +18,6 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Game.Configuration;
-using osu.Game.Screens.Play;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Objects.Drawables
@@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
public readonly Bindable AccentColour = new Bindable(Color4.Gray);
- protected SkinnableSound Samples { get; private set; }
+ protected PausableSkinnableSound Samples { get; private set; }
public virtual IEnumerable GetSamples() => HitObject.Samples;
@@ -180,7 +179,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
}
- Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)));
+ Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)));
AddInternal(Samples);
}
@@ -360,9 +359,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
}
- [Resolved(canBeNull: true)]
- private GameplayClock gameplayClock { get; set; }
-
///
/// Calculate the position to be used for sample playback at a specified X position (0..1).
///
@@ -375,18 +371,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0);
}
- ///
- /// Whether samples should currently be playing. Will be false during seek operations.
- ///
- protected bool ShouldPlaySamples => gameplayClock?.IsSeeking != true;
-
///
/// Plays all the hit sounds for this .
/// This is invoked automatically when this is hit.
///
public virtual void PlaySamples()
{
- if (Samples != null && ShouldPlaySamples)
+ if (Samples != null)
{
Samples.Balance.Value = CalculateSamplePlaybackBalance(SamplePlaybackPosition);
Samples.Play();
diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs
index 7a02db190a..4224f7f9e1 100644
--- a/osu.Game/Rulesets/Scoring/HitResult.cs
+++ b/osu.Game/Rulesets/Scoring/HitResult.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Scoring
///
[Description(@"")]
[Order(14)]
- None = 0,
+ None,
///
/// Indicates that the object has been judged as a miss.
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Scoring
///
[Description(@"Miss")]
[Order(5)]
- Miss = 64,
+ Miss,
[Description(@"Meh")]
[Order(4)]
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Scoring
/// Indicates small tick miss.
///
[Order(11)]
- SmallTickMiss = 128,
+ SmallTickMiss,
///
/// Indicates a small tick hit.
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Scoring
/// Indicates a large tick miss.
///
[Order(10)]
- LargeTickMiss = 192,
+ LargeTickMiss,
///
/// Indicates a large tick hit.
@@ -85,20 +85,20 @@ namespace osu.Game.Rulesets.Scoring
///
[Description("S Bonus")]
[Order(9)]
- SmallBonus = 254,
+ SmallBonus,
///
/// Indicates a large bonus.
///
[Description("L Bonus")]
[Order(8)]
- LargeBonus = 320,
+ LargeBonus,
///
/// Indicates a miss that should be ignored for scoring purposes.
///
[Order(13)]
- IgnoreMiss = 384,
+ IgnoreMiss,
///
/// Indicates a hit that should be ignored for scoring purposes.
diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
index a4af92749f..55c4edfbd1 100644
--- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
+++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
@@ -2,7 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
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.Timing;
@@ -59,7 +62,7 @@ namespace osu.Game.Rulesets.UI
{
if (clock != null)
{
- stabilityGameplayClock.ParentGameplayClock = parentGameplayClock = clock;
+ parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock;
GameplayClock.IsPaused.BindTo(clock.IsPaused);
}
}
@@ -215,7 +218,9 @@ namespace osu.Game.Rulesets.UI
private class StabilityGameplayClock : GameplayClock
{
- public IFrameBasedClock ParentGameplayClock;
+ public GameplayClock ParentGameplayClock;
+
+ public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>();
public StabilityGameplayClock(FramedClock underlyingClock)
: base(underlyingClock)
diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
index c68eeeb4f9..0a8c339559 100644
--- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
+++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
@@ -3,8 +3,8 @@
using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
-using System;
using osu.Framework.Allocation;
+using osu.Game.Extensions;
using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Components
@@ -35,9 +35,7 @@ namespace osu.Game.Screens.Edit.Components
protected override void Update()
{
base.Update();
-
- var timespan = TimeSpan.FromMilliseconds(editorClock.CurrentTime);
- trackTimer.Text = $"{(timespan < TimeSpan.Zero ? "-" : string.Empty)}{timespan:mm\\:ss\\:fff}";
+ trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
}
}
}
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 102955657e..8c0e31c04c 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
@@ -1,70 +1,50 @@
// 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.Specialized;
using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Graphics;
-using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
{
///
/// The part of the timeline that displays the control points.
///
- public class ControlPointPart : TimelinePart
+ public class ControlPointPart : TimelinePart
{
+ private IBindableList controlPointGroups;
+
protected override void LoadBeatmap(WorkingBeatmap beatmap)
{
base.LoadBeatmap(beatmap);
- ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo;
-
- cpi.TimingPoints.ForEach(addTimingPoint);
-
- // Consider all non-timing points as the same type
- cpi.SamplePoints.Select(c => (ControlPoint)c)
- .Concat(cpi.EffectPoints)
- .Concat(cpi.DifficultyPoints)
- .Distinct()
- // Non-timing points should not be added where there are timing points
- .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time)
- .ForEach(addNonTimingPoint);
- }
-
- private void addTimingPoint(ControlPoint controlPoint) => Add(new TimingPointVisualisation(controlPoint));
- private void addNonTimingPoint(ControlPoint controlPoint) => Add(new NonTimingPointVisualisation(controlPoint));
-
- private class TimingPointVisualisation : ControlPointVisualisation
- {
- public TimingPointVisualisation(ControlPoint controlPoint)
- : base(controlPoint)
+ controlPointGroups = beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy();
+ controlPointGroups.BindCollectionChanged((sender, args) =>
{
- }
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Reset:
+ Clear();
+ break;
- [BackgroundDependencyLoader]
- private void load(OsuColour colours) => Colour = colours.YellowDark;
- }
+ case NotifyCollectionChangedAction.Add:
+ foreach (var group in args.NewItems.OfType())
+ Add(new GroupVisualisation(group));
+ break;
- private class NonTimingPointVisualisation : ControlPointVisualisation
- {
- public NonTimingPointVisualisation(ControlPoint controlPoint)
- : base(controlPoint)
- {
- }
+ case NotifyCollectionChangedAction.Remove:
+ foreach (var group in args.OldItems.OfType())
+ {
+ var matching = Children.SingleOrDefault(gv => gv.Group == group);
- [BackgroundDependencyLoader]
- private void load(OsuColour colours) => Colour = colours.Green;
- }
+ matching?.Expire();
+ }
- private abstract class ControlPointVisualisation : PointVisualisation
- {
- protected ControlPointVisualisation(ControlPoint controlPoint)
- : base(controlPoint.Time)
- {
- }
+ break;
+ }
+ }, true);
}
}
}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs
new file mode 100644
index 0000000000..b9eb53b697
--- /dev/null
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.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 System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics;
+using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
+{
+ public class GroupVisualisation : PointVisualisation
+ {
+ public readonly ControlPointGroup Group;
+
+ private BindableList controlPoints;
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ public GroupVisualisation(ControlPointGroup group)
+ : base(group.Time)
+ {
+ Group = group;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy();
+ controlPoints.BindCollectionChanged((_, __) =>
+ {
+ if (controlPoints.Count == 0)
+ {
+ Colour = Color4.Transparent;
+ return;
+ }
+
+ Colour = controlPoints.Any(c => c is TimingControlPoint) ? colours.YellowDark : colours.Green;
+ }, true);
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs
index b99a053859..d870eb5279 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs
@@ -14,9 +14,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public class TimelineArea : Container
{
- private readonly Timeline timeline = new Timeline { RelativeSizeAxes = Axes.Both };
+ public readonly Timeline Timeline = new Timeline { RelativeSizeAxes = Axes.Both };
- protected override Container Content => timeline;
+ protected override Container Content => Timeline;
[BackgroundDependencyLoader]
private void load()
@@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
}
},
- timeline
+ Timeline
},
},
ColumnDimensions = new[]
@@ -121,9 +121,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
waveformCheckbox.Current.Value = true;
- timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
+ Timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
}
- private void changeZoom(float change) => timeline.Zoom += change;
+ private void changeZoom(float change) => Timeline.Zoom += change;
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
index b95b3842b3..bc2ccfc605 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
@@ -3,18 +3,23 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
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.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
using osuTK.Graphics;
@@ -34,11 +39,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private readonly List shadowComponents = new List();
+ private DrawableHitObject drawableHitObject;
+
+ private Bindable comboColour;
+
+ private readonly Container mainComponents;
+
+ private readonly OsuSpriteText comboIndexText;
+
+ private Bindable comboIndex;
+
private const float thickness = 5;
private const float shadow_radius = 5;
- private const float circle_size = 16;
+ private const float circle_size = 24;
public TimelineHitObjectBlueprint(HitObject hitObject)
: base(hitObject)
@@ -54,14 +69,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
+ AddRangeInternal(new Drawable[]
+ {
+ mainComponents = new Container
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ },
+ comboIndexText = new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.Centre,
+ Font = OsuFont.Numeric.With(size: circle_size / 2, weight: FontWeight.Black),
+ },
+ });
+
circle = new Circle
{
Size = new Vector2(circle_size),
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
- RelativePositionAxes = Axes.X,
- AlwaysPresent = true,
- Colour = Color4.White,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
@@ -77,7 +106,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
DragBar dragBarUnderlay;
Container extensionBar;
- AddRangeInternal(new Drawable[]
+ mainComponents.AddRange(new Drawable[]
{
extensionBar = new Container
{
@@ -117,12 +146,54 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
else
{
- AddInternal(circle);
+ mainComponents.Add(circle);
}
updateShadows();
}
+ [BackgroundDependencyLoader(true)]
+ private void load(HitObjectComposer composer)
+ {
+ if (composer != null)
+ {
+ // best effort to get the drawable representation for grabbing colour and what not.
+ drawableHitObject = composer.HitObjects.FirstOrDefault(d => d.HitObject == HitObject);
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (HitObject is IHasComboInformation comboInfo)
+ {
+ comboIndex = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
+ comboIndex.BindValueChanged(combo =>
+ {
+ comboIndexText.Text = (combo.NewValue + 1).ToString();
+ }, true);
+ }
+
+ if (drawableHitObject != null)
+ {
+ comboColour = drawableHitObject.AccentColour.GetBoundCopy();
+ comboColour.BindValueChanged(colour =>
+ {
+ if (HitObject is IHasDuration)
+ mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White);
+ else
+ mainComponents.Colour = drawableHitObject.AccentColour.Value;
+
+ var col = mainComponents.Colour.TopLeft.Linear;
+ float brightness = col.R + col.G + col.B;
+
+ // decide the combo index colour based on brightness?
+ comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White;
+ }, true);
+ }
+ }
+
protected override void Update()
{
base.Update();
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index d7a4661fa0..5282b4d998 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -1,8 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Skinning;
@@ -18,11 +22,23 @@ namespace osu.Game.Screens.Edit.Compose
{
}
- protected override Drawable CreateMainContent()
+ private Ruleset ruleset;
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance();
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ ruleset = parent.Get>().Value.BeatmapInfo.Ruleset?.CreateInstance();
composer = ruleset?.CreateHitObjectComposer();
+ // make the composer available to the timeline and other components in this screen.
+ dependencies.CacheAs(composer);
+
+ return dependencies;
+ }
+
+ protected override Drawable CreateMainContent()
+ {
if (ruleset == null || composer == null)
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index fd090e0959..a0692d94e6 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -107,6 +107,7 @@ namespace osu.Game.Screens.Edit
UpdateClockSource();
dependencies.CacheAs(clock);
+ dependencies.CacheAs(clock);
AddInternal(clock);
// todo: remove caching of this and consume via editorBeatmap?
diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs
index ec203df064..4b7cd82637 100644
--- a/osu.Game/Screens/Edit/EditorClock.cs
+++ b/osu.Game/Screens/Edit/EditorClock.cs
@@ -7,17 +7,18 @@ using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transforms;
-using osu.Framework.Utils;
using osu.Framework.Timing;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Screens.Play;
namespace osu.Game.Screens.Edit
{
///
/// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor.
///
- public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock
+ public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISamplePlaybackDisabler
{
public IBindable
- public class GameplayClock : IFrameBasedClock
+ public class GameplayClock : IFrameBasedClock, ISamplePlaybackDisabler
{
private readonly IFrameBasedClock underlyingClock;
public readonly BindableBool IsPaused = new BindableBool();
+ ///
+ /// All adjustments applied to this clock which don't come from gameplay or mods.
+ ///
+ public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>();
+
public GameplayClock(IFrameBasedClock underlyingClock)
{
this.underlyingClock = underlyingClock;
@@ -29,6 +36,23 @@ namespace osu.Game.Screens.Play
public double Rate => underlyingClock.Rate;
+ ///
+ /// The rate of gameplay when playback is at 100%.
+ /// This excludes any seeking / user adjustments.
+ ///
+ public double TrueGameplayRate
+ {
+ get
+ {
+ double baseRate = Rate;
+
+ foreach (var adjustment in NonGameplayAdjustments)
+ baseRate /= adjustment.Value;
+
+ return baseRate;
+ }
+ }
+
public bool IsRunning => underlyingClock.IsRunning;
///
@@ -48,5 +72,7 @@ namespace osu.Game.Screens.Play
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
public IClock Source => underlyingClock;
+
+ public IBindable SamplePlaybackDisabled => IsPaused;
}
}
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 7a9cb3dddd..9f8e55f577 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
@@ -50,8 +51,11 @@ namespace osu.Game.Screens.Play
///
/// The final clock which is exposed to underlying components.
///
- [Cached]
- public readonly GameplayClock GameplayClock;
+ public GameplayClock GameplayClock => localGameplayClock;
+
+ [Cached(typeof(GameplayClock))]
+ [Cached(typeof(ISamplePlaybackDisabler))]
+ private readonly LocalGameplayClock localGameplayClock;
private Bindable userAudioOffset;
@@ -79,7 +83,7 @@ namespace osu.Game.Screens.Play
userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock);
// the clock to be exposed via DI to children.
- GameplayClock = new GameplayClock(userOffsetClock);
+ localGameplayClock = new LocalGameplayClock(userOffsetClock);
GameplayClock.IsPaused.BindTo(IsPaused);
}
@@ -200,7 +204,9 @@ namespace osu.Game.Screens.Play
protected override void Update()
{
if (!IsPaused.Value)
+ {
userOffsetClock.ProcessFrame();
+ }
base.Update();
}
@@ -215,6 +221,9 @@ namespace osu.Game.Screens.Play
track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
+ localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust);
+ localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate);
+
speedAdjustmentsApplied = true;
}
@@ -231,9 +240,24 @@ namespace osu.Game.Screens.Play
track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
+ localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust);
+ localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate);
+
speedAdjustmentsApplied = false;
}
+ private class LocalGameplayClock : GameplayClock
+ {
+ public readonly List> MutableNonGameplayAdjustments = new List>();
+
+ public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments;
+
+ public LocalGameplayClock(FramedOffsetClock underlyingClock)
+ : base(underlyingClock)
+ {
+ }
+ }
+
private class HardwareCorrectionOffsetClock : FramedOffsetClock
{
// we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this.
diff --git a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs
new file mode 100644
index 0000000000..6b37021fe6
--- /dev/null
+++ b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs
@@ -0,0 +1,20 @@
+// 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;
+using osu.Game.Skinning;
+
+namespace osu.Game.Screens.Play
+{
+ ///
+ /// Allows a component to disable sample playback dynamically as required.
+ /// Handled by .
+ ///
+ public interface ISamplePlaybackDisabler
+ {
+ ///
+ /// Whether sample playback should be disabled (or paused for looping samples).
+ ///
+ IBindable SamplePlaybackDisabled { get; }
+ }
+}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 8e2ed583f2..175722c44e 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -191,9 +191,25 @@ namespace osu.Game.Screens.Play
dependencies.CacheAs(gameplayBeatmap);
- addUnderlayComponents(GameplayClockContainer);
- addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap);
- addOverlayComponents(GameplayClockContainer, Beatmap.Value);
+ var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
+
+ // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
+ // full access to all skin sources.
+ var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
+
+ // load the skinning hierarchy first.
+ // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
+ GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider));
+
+ rulesetSkinProvider.AddRange(new[]
+ {
+ // underlay and gameplay should have access the to skinning sources.
+ createUnderlayComponents(),
+ createGameplayComponents(Beatmap.Value, playableBeatmap)
+ });
+
+ // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
+ GameplayClockContainer.Add(createOverlayComponents(Beatmap.Value));
if (!DrawableRuleset.AllowGameplayOverlays)
{
@@ -238,45 +254,31 @@ namespace osu.Game.Screens.Play
breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
}
- private void addUnderlayComponents(Container target)
+ private Drawable createUnderlayComponents() =>
+ DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both };
+
+ private Drawable createGameplayComponents(WorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay)
{
- target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both });
- }
-
- private void addGameplayComponents(Container target, WorkingBeatmap working, IBeatmap playableBeatmap)
- {
- var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin);
-
- // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
- // full access to all skin sources.
- var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
-
- // load the skinning hierarchy first.
- // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
- target.Add(new ScalingContainer(ScalingMode.Gameplay)
- .WithChild(beatmapSkinProvider
- .WithChild(target = rulesetSkinProvider)));
-
- target.AddRange(new Drawable[]
+ Children = new Drawable[]
{
- DrawableRuleset,
+ DrawableRuleset.With(r =>
+ r.FrameStableComponents.Children = new Drawable[]
+ {
+ ScoreProcessor,
+ HealthProcessor,
+ breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
+ {
+ Breaks = working.Beatmap.Breaks
+ }
+ }),
new ComboEffects(ScoreProcessor)
- });
+ }
+ };
- DrawableRuleset.FrameStableComponents.AddRange(new Drawable[]
- {
- ScoreProcessor,
- HealthProcessor,
- breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
- {
- Breaks = working.Beatmap.Breaks
- }
- });
- }
-
- private void addOverlayComponents(Container target, WorkingBeatmap working)
+ private Drawable createOverlayComponents(WorkingBeatmap working) => new Container
{
- target.AddRange(new[]
+ RelativeSizeAxes = Axes.Both,
+ Children = new[]
{
DimmableStoryboard.OverlayLayerContainer.CreateProxy(),
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
@@ -342,8 +344,8 @@ namespace osu.Game.Screens.Play
},
},
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
- });
- }
+ }
+ };
private void onBreakTimeChanged(ValueChangedEvent isBreakTime)
{
diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs
new file mode 100644
index 0000000000..991278fb98
--- /dev/null
+++ b/osu.Game/Skinning/PausableSkinnableSound.cs
@@ -0,0 +1,66 @@
+// 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 osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Game.Audio;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Skinning
+{
+ public class PausableSkinnableSound : SkinnableSound
+ {
+ protected bool RequestedPlaying { get; private set; }
+
+ public PausableSkinnableSound(ISampleInfo hitSamples)
+ : base(hitSamples)
+ {
+ }
+
+ public PausableSkinnableSound(IEnumerable hitSamples)
+ : base(hitSamples)
+ {
+ }
+
+ private readonly IBindable samplePlaybackDisabled = new Bindable();
+
+ [BackgroundDependencyLoader(true)]
+ private void load(ISamplePlaybackDisabler samplePlaybackDisabler)
+ {
+ // if in a gameplay context, pause sample playback when gameplay is paused.
+ if (samplePlaybackDisabler != null)
+ {
+ samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
+ samplePlaybackDisabled.BindValueChanged(disabled =>
+ {
+ if (RequestedPlaying)
+ {
+ if (disabled.NewValue)
+ base.Stop();
+ // it's not easy to know if a sample has finished playing (to end).
+ // to keep things simple only resume playing looping samples.
+ else if (Looping)
+ base.Play();
+ }
+ });
+ }
+ }
+
+ public override void Play()
+ {
+ RequestedPlaying = true;
+
+ if (samplePlaybackDisabled.Value)
+ return;
+
+ base.Play();
+ }
+
+ public override void Stop()
+ {
+ RequestedPlaying = false;
+ base.Stop();
+ }
+ }
+}
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index ba14049b41..f6e91811dd 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -11,7 +11,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
-using osu.Game.Screens.Play;
namespace osu.Game.Skinning
{
@@ -22,8 +21,6 @@ namespace osu.Game.Skinning
[Resolved]
private ISampleStore samples { get; set; }
- private bool requestedPlaying;
-
public override bool RemoveWhenNotAlive => false;
public override bool RemoveCompletedTransforms => false;
@@ -37,7 +34,7 @@ namespace osu.Game.Skinning
///
protected bool PlayWhenZeroVolume => Looping;
- private readonly AudioContainer samplesContainer;
+ protected readonly AudioContainer SamplesContainer;
public SkinnableSound(ISampleInfo hitSamples)
: this(new[] { hitSamples })
@@ -47,28 +44,7 @@ namespace osu.Game.Skinning
public SkinnableSound(IEnumerable hitSamples)
{
this.hitSamples = hitSamples.ToArray();
- InternalChild = samplesContainer = new AudioContainer();
- }
-
- private Bindable gameplayClockPaused;
-
- [BackgroundDependencyLoader(true)]
- private void load(GameplayClock gameplayClock)
- {
- // if in a gameplay context, pause sample playback when gameplay is paused.
- gameplayClockPaused = gameplayClock?.IsPaused.GetBoundCopy();
- gameplayClockPaused?.BindValueChanged(paused =>
- {
- if (requestedPlaying)
- {
- if (paused.NewValue)
- stop();
- // it's not easy to know if a sample has finished playing (to end).
- // to keep things simple only resume playing looping samples.
- else if (Looping)
- play();
- }
- });
+ InternalChild = SamplesContainer = new AudioContainer();
}
private bool looping;
@@ -82,34 +58,22 @@ namespace osu.Game.Skinning
looping = value;
- samplesContainer.ForEach(c => c.Looping = looping);
+ SamplesContainer.ForEach(c => c.Looping = looping);
}
}
- public void Play()
+ public virtual void Play()
{
- requestedPlaying = true;
- play();
- }
-
- private void play()
- {
- samplesContainer.ForEach(c =>
+ SamplesContainer.ForEach(c =>
{
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
c.Play();
});
}
- public void Stop()
+ public virtual void Stop()
{
- requestedPlaying = false;
- stop();
- }
-
- private void stop()
- {
- samplesContainer.ForEach(c => c.Stop());
+ SamplesContainer.ForEach(c => c.Stop());
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
@@ -138,33 +102,33 @@ namespace osu.Game.Skinning
return ch;
}).Where(c => c != null);
- samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
+ SamplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
// Start playback internally for the new samples if the previous ones were playing beforehand.
if (wasPlaying)
- play();
+ Play();
}
#region Re-expose AudioContainer
- public BindableNumber Volume => samplesContainer.Volume;
+ public BindableNumber Volume => SamplesContainer.Volume;
- public BindableNumber Balance => samplesContainer.Balance;
+ public BindableNumber Balance => SamplesContainer.Balance;
- public BindableNumber Frequency => samplesContainer.Frequency;
+ public BindableNumber Frequency => SamplesContainer.Frequency;
- public BindableNumber Tempo => samplesContainer.Tempo;
+ public BindableNumber Tempo => SamplesContainer.Tempo;
public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable)
- => samplesContainer.AddAdjustment(type, adjustBindable);
+ => SamplesContainer.AddAdjustment(type, adjustBindable);
public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable)
- => samplesContainer.RemoveAdjustment(type, adjustBindable);
+ => SamplesContainer.RemoveAdjustment(type, adjustBindable);
public void RemoveAllAdjustments(AdjustableProperty type)
- => samplesContainer.RemoveAllAdjustments(type);
+ => SamplesContainer.RemoveAllAdjustments(type);
- public bool IsPlaying => samplesContainer.Any(s => s.Playing);
+ public bool IsPlaying => SamplesContainer.Any(s => s.Playing);
#endregion
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
index 119c48836b..08811b9b8c 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
@@ -4,15 +4,13 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
+using osu.Game.Skinning;
namespace osu.Game.Storyboards.Drawables
{
- public class DrawableStoryboardSample : Component
+ public class DrawableStoryboardSample : PausableSkinnableSound
{
///
/// The amount of time allowable beyond the start time of the sample, for the sample to start.
@@ -21,38 +19,37 @@ namespace osu.Game.Storyboards.Drawables
private readonly StoryboardSampleInfo sampleInfo;
- protected SampleChannel Channel { get; private set; }
-
public override bool RemoveWhenNotAlive => false;
public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo)
+ : base(sampleInfo)
{
this.sampleInfo = sampleInfo;
LifetimeStart = sampleInfo.StartTime;
}
- [BackgroundDependencyLoader]
- private void load(IBindable beatmap, IBindable> mods)
- {
- Channel = beatmap.Value.Skin.GetSample(sampleInfo);
- if (Channel == null)
- return;
+ [Resolved]
+ private IBindable> mods { get; set; }
- Channel.Volume.Value = sampleInfo.Volume / 100.0;
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
foreach (var mod in mods.Value.OfType())
- mod.ApplyToSample(Channel);
+ {
+ foreach (var sample in SamplesContainer)
+ mod.ApplyToSample(sample);
+ }
}
protected override void Update()
{
base.Update();
- // TODO: this logic will need to be consolidated with other game samples like hit sounds.
if (Time.Current < sampleInfo.StartTime)
{
// We've rewound before the start time of the sample
- Channel?.Stop();
+ Stop();
// In the case that the user fast-forwards to a point far beyond the start time of the sample,
// we want to be able to fall into the if-conditional below (therefore we must not have a life time end)
@@ -63,8 +60,8 @@ namespace osu.Game.Storyboards.Drawables
{
// We've passed the start time of the sample. We only play the sample if we're within an allowable range
// from the sample's start, to reduce layering if we've been fast-forwarded far into the future
- if (Time.Current - sampleInfo.StartTime < allowable_late_start)
- Channel?.Play();
+ if (!RequestedPlaying && Time.Current - sampleInfo.StartTime < allowable_late_start)
+ Play();
// In the case that the user rewinds to a point far behind the start time of the sample,
// we want to be able to fall into the if-conditional above (therefore we must not have a life time start)
@@ -72,13 +69,5 @@ namespace osu.Game.Storyboards.Drawables
LifetimeEnd = sampleInfo.StartTime;
}
}
-
- protected override void Dispose(bool isDisposing)
- {
- Channel?.Stop();
- Channel = null;
-
- base.Dispose(isDisposing);
- }
}
}
diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs
index f0ec638fc9..693c9cb792 100644
--- a/osu.Game/Tests/Visual/EditorClockTestScene.cs
+++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual
protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor();
protected new readonly EditorClock Clock;
+ protected virtual bool ScrollUsingMouseWheel => true;
+
protected EditorClockTestScene()
{
Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false };
@@ -57,6 +59,9 @@ namespace osu.Game.Tests.Visual
protected override bool OnScroll(ScrollEvent e)
{
+ if (!ScrollUsingMouseWheel)
+ return false;
+
if (e.ScrollDelta.Y > 0)
Clock.SeekBackward(true);
else
diff --git a/osu.Game/Users/UserListPanel.cs b/osu.Game/Users/UserListPanel.cs
index 9c95eff739..cc4fca9b94 100644
--- a/osu.Game/Users/UserListPanel.cs
+++ b/osu.Game/Users/UserListPanel.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Users
private void load()
{
Background.Width = 0.5f;
+ Background.Origin = Anchor.CentreRight;
+ Background.Anchor = Anchor.CentreRight;
Background.Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(1), Color4.White.Opacity(0.3f));
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 6412f707d0..5fa1685d9b 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index f1e13169a5..60708a39e2 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -80,7 +80,7 @@
-
+