diff --git a/osu.Android.props b/osu.Android.props
index 215a9a8090..2c3c8bcaad 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -60,7 +60,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 6f1a7873ec..71e05083be 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch
{
public class CatchRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index f4218061d4..00734810b3 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
- protected override void UpdateInitialTransforms() => this.FadeIn(200);
+ protected override void UpdateInitialTransforms() => this.FadeInFromZero(200);
protected override void UpdateStateTransforms(ArmedState state)
{
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index f48b84e344..6b7f00c5d0 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Catch.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
- TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
+ TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
}
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
index e5f379f608..97d8aaa052 100644
--- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
+++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit
{
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
- public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 2729621ab3..0bfe6f9517 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit
[Cached(Type = typeof(IManiaHitObjectComposer))]
public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer
{
- protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; }
+ private DrawableManiaEditRuleset drawableRuleset;
public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset)
@@ -33,23 +33,23 @@ namespace osu.Game.Rulesets.Mania.Edit
///
/// The screen-space position.
/// The column which intersects with .
- public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition);
+ public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition);
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns;
+ public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns;
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
{
- DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
+ drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
// This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it
- dependencies.CacheAs(DrawableRuleset.ScrollingInfo);
+ dependencies.CacheAs(drawableRuleset.ScrollingInfo);
- return DrawableRuleset;
+ return drawableRuleset;
}
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 8966b5058f..0de86c2149 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania
{
public class ManiaRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index ce1484d460..e5b114ca81 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -46,8 +46,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
- protected override void UpdateInitialTransforms() => this.FadeIn();
-
protected override void UpdateStateTransforms(ArmedState state)
{
switch (state)
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index c8aeda8fe4..f26526fe70 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -36,11 +36,13 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable BarLines;
+ protected override bool RelativeScaleBeatLengths => true;
+
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
private readonly Bindable configDirection = new Bindable();
- public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
// Generate the bar lines
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
index bcb6099cfb..cc08d356f9 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class DrawableOsuEditRuleset : DrawableOsuRuleset
{
- public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index c5452ae0aa..1c040e9dee 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
=> new DrawableOsuEditRuleset(ruleset, beatmap, mods);
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index ca124e9214..0af278f6a4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly HitArea hitArea;
+ private readonly SkinnableDrawable mainContent;
+
public DrawableHitCircle(HitCircle h)
: base(h)
{
@@ -56,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
- new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
+ mainContent = new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
@@ -108,6 +110,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateInitialTransforms();
+ mainContent.FadeInFromZero(HitObject.TimeFadeIn);
+
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
ApproachCircle.Expire(true);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index a89fb8b682..b4f5642f45 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
- protected override void UpdateInitialTransforms() => this.FadeIn(HitObject.TimeFadeIn);
-
private OsuInputManager osuActionInputManager;
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index a0626707af..1749ea1f60 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -93,6 +93,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
+ protected override void UpdateInitialTransforms()
+ {
+ base.UpdateInitialTransforms();
+
+ Body.FadeInFromZero(HitObject.TimeFadeIn);
+ }
+
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index d50d4f401c..3bbfc25d22 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu
{
public class OsuRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
index d185d7d4c9..aa61fb6922 100644
--- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
- public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableOsuRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 5f3bab5b28..5424ccb4de 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -78,8 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public abstract bool OnPressed(TaikoAction action);
public virtual bool OnReleased(TaikoAction action) => false;
- protected override void UpdateInitialTransforms() => this.FadeIn();
-
public override double LifetimeStart
{
get => base.LifetimeStart;
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 83356b77c2..6d0a5eb1e1 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko
{
public class TaikoRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index ec3a56e9c7..b03bea578e 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableTaikoRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Left;
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index e62dc45cab..a10f70a344 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -44,9 +44,8 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly JudgementContainer judgementContainer;
internal readonly HitTarget HitTarget;
- private readonly Container topLevelHitContainer;
-
- private readonly Container barlineContainer;
+ private readonly ProxyContainer topLevelHitContainer;
+ private readonly ProxyContainer barlineContainer;
private readonly Container overlayBackgroundContainer;
private readonly Container backgroundContainer;
@@ -108,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
},
- barlineContainer = new Container
+ barlineContainer = new ProxyContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }
@@ -183,7 +182,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
},
- topLevelHitContainer = new Container
+ topLevelHitContainer = new ProxyContainer
{
Name = "Top level hit objects",
RelativeSizeAxes = Axes.Both,
@@ -256,5 +255,15 @@ namespace osu.Game.Rulesets.Taiko.UI
break;
}
}
+
+ private class ProxyContainer : LifetimeManagementContainer
+ {
+ public new MarginPadding Padding
+ {
+ set => base.Padding = value;
+ }
+
+ public void Add(Drawable proxy) => AddInternal(proxy);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
index 7accbe2fa8..0ea73fb3de 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
@@ -16,15 +16,13 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
- [Cached(Type = typeof(IPlacementHandler))]
- public class TestSceneHitObjectComposer : OsuTestScene, IPlacementHandler
+ public class TestSceneHitObjectComposer : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -39,8 +37,6 @@ namespace osu.Game.Tests.Visual.Editor
typeof(HitCirclePlacementBlueprint),
};
- private HitObjectComposer composer;
-
[BackgroundDependencyLoader]
private void load()
{
@@ -67,15 +63,7 @@ namespace osu.Game.Tests.Visual.Editor
Dependencies.CacheAs(clock);
Dependencies.CacheAs(clock);
- Child = composer = new OsuHitObjectComposer(new OsuRuleset());
+ Child = new OsuHitObjectComposer(new OsuRuleset());
}
-
- public void BeginPlacement(HitObject hitObject)
- {
- }
-
- public void EndPlacement(HitObject hitObject) => composer.Add(hitObject);
-
- public void Delete(HitObject hitObject) => composer.Remove(hitObject);
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
new file mode 100644
index 0000000000..60ace8ea69
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -0,0 +1,305 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Framework.MathUtils;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneDrawableScrollingRuleset : OsuTestScene
+ {
+ ///
+ /// The amount of time visible by the "view window" of the playfield.
+ /// All hitobjects added through are spaced apart by this value, such that for a beat length of 1000,
+ /// there will be at most 2 hitobjects visible in the "view window".
+ ///
+ private const double time_range = 1000;
+
+ private readonly ManualClock testClock = new ManualClock();
+ private TestDrawableScrollingRuleset drawableRuleset;
+
+ [SetUp]
+ public void Setup() => Schedule(() => testClock.CurrentTime = 0);
+
+ [Test]
+ public void TestRelativeBeatLengthScaleSingleTimingPoint()
+ {
+ var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 });
+
+ createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
+
+ assertPosition(0, 0f);
+
+ // The single timing point is 1x speed relative to itself, such that the hitobject occurring time_range milliseconds later should appear
+ // at the bottom of the view window regardless of the timing point's beat length
+ assertPosition(1, 1f);
+ }
+
+ [Test]
+ public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant()
+ {
+ var beatmap = createBeatmap(
+ new TimingControlPoint { BeatLength = time_range / 2 },
+ new TimingControlPoint { Time = 12000, BeatLength = time_range },
+ new TimingControlPoint { Time = 100000, BeatLength = time_range });
+
+ createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
+
+ assertPosition(0, 0f);
+ assertPosition(1, 1f);
+ }
+
+ [Test]
+ public void TestRelativeBeatLengthScaleFromSecondTimingPoint()
+ {
+ var beatmap = createBeatmap(
+ new TimingControlPoint { BeatLength = time_range },
+ new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
+
+ createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
+
+ // The first timing point should have a relative velocity of 2
+ assertPosition(0, 0f);
+ assertPosition(1, 0.5f);
+ assertPosition(2, 1f);
+
+ // Move to the second timing point
+ setTime(3 * time_range);
+ assertPosition(3, 0f);
+
+ // As above, this is the timing point that is 1x speed relative to itself, so the hitobject occurring time_range milliseconds later should be at the bottom of the view window
+ assertPosition(4, 1f);
+ }
+
+ [Test]
+ public void TestNonRelativeScale()
+ {
+ var beatmap = createBeatmap(
+ new TimingControlPoint { BeatLength = time_range },
+ new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
+
+ createTest(beatmap);
+
+ assertPosition(0, 0f);
+ assertPosition(1, 1);
+
+ // Move to the second timing point
+ setTime(3 * time_range);
+ assertPosition(3, 0f);
+
+ // For a beat length of 500, the view window of this timing point is elongated 2x (1000 / 500), such that the second hitobject is two TimeRanges away (offscreen)
+ // To bring it on-screen, half TimeRange is added to the current time, bringing the second half of the view window into view, and the hitobject should appear at the bottom
+ setTime(3 * time_range + time_range / 2);
+ assertPosition(4, 1f);
+ }
+
+ private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}",
+ () => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
+
+ private void setTime(double time)
+ {
+ AddStep($"set time = {time}", () => testClock.CurrentTime = time);
+ }
+
+ ///
+ /// Creates an , containing 10 hitobjects and user-provided timing points.
+ /// The hitobjects are spaced milliseconds apart.
+ ///
+ /// The timing points to add to the beatmap.
+ /// The .
+ private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints)
+ {
+ var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
+
+ beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints);
+
+ for (int i = 0; i < 10; i++)
+ beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
+
+ return beatmap;
+ }
+
+ private void createTest(IBeatmap beatmap, Action overrideAction = null) => AddStep("create test", () =>
+ {
+ var ruleset = new TestScrollingRuleset();
+
+ drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty());
+ drawableRuleset.FrameStablePlayback = false;
+
+ overrideAction?.Invoke(drawableRuleset);
+
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Height = 0.75f,
+ Width = 400,
+ Masking = true,
+ Clock = new FramedClock(testClock),
+ Child = drawableRuleset
+ };
+ });
+
+ #region Ruleset
+
+ private class TestScrollingRuleset : Ruleset
+ {
+ public TestScrollingRuleset(RulesetInfo rulesetInfo = null)
+ : base(rulesetInfo)
+ {
+ }
+
+ public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException();
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods);
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap);
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
+
+ public override string Description { get; } = string.Empty;
+
+ public override string ShortName { get; } = string.Empty;
+ }
+
+ private class TestDrawableScrollingRuleset : DrawableScrollingRuleset
+ {
+ public bool RelativeScaleBeatLengthsOverride { get; set; }
+
+ protected override bool RelativeScaleBeatLengths => RelativeScaleBeatLengthsOverride;
+
+ protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
+
+ public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ : base(ruleset, beatmap, mods)
+ {
+ TimeRange.Value = time_range;
+ }
+
+ public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h);
+
+ protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
+
+ protected override Playfield CreatePlayfield() => new TestPlayfield();
+ }
+
+ private class TestPlayfield : ScrollingPlayfield
+ {
+ public TestPlayfield()
+ {
+ AddInternal(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.2f,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Top = 150 },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Height = 2,
+ Colour = Color4.Green
+ },
+ HitObjectContainer
+ }
+ }
+ }
+ });
+ }
+ }
+
+ private class TestBeatmapConverter : BeatmapConverter
+ {
+ public TestBeatmapConverter(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
+ protected override IEnumerable ValidConversionTypes => new[] { typeof(HitObject) };
+
+ protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
+ {
+ yield return new TestHitObject
+ {
+ StartTime = original.StartTime,
+ EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100)
+ };
+ }
+ }
+
+ #endregion
+
+ #region HitObject
+
+ private class TestHitObject : HitObject, IHasEndTime
+ {
+ public double EndTime { get; set; }
+
+ public double Duration => EndTime - StartTime;
+ }
+
+ private class DrawableTestHitObject : DrawableHitObject
+ {
+ public DrawableTestHitObject(TestHitObject hitObject)
+ : base(hitObject)
+ {
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+
+ Size = new Vector2(100, 25);
+
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.LightPink
+ },
+ new Box
+ {
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = 2,
+ Colour = Color4.Red
+ }
+ });
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index a09a1bb9cb..5435e86dfd 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps
///
/// A Beatmap containing converted HitObjects.
///
- public class Beatmap : IBeatmap
+ public class Beatmap : IBeatmap
where T : HitObject
{
public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo
@@ -36,17 +36,13 @@ namespace osu.Game.Beatmaps
public List Breaks { get; set; } = new List();
- ///
- /// Total amount of break time in the beatmap.
- ///
[JsonIgnore]
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
- ///
- /// The HitObjects this Beatmap contains.
- ///
[JsonConverter(typeof(TypedListConverter))]
- public List HitObjects = new List();
+ public List HitObjects { get; set; } = new List();
+
+ IReadOnlyList IBeatmap.HitObjects => HitObjects;
IReadOnlyList IBeatmap.HitObjects => HitObjects;
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index e5815a3f3b..ccb8a92b3a 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -14,6 +14,8 @@ namespace osu.Game.Beatmaps.ControlPoints
///
public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
+ public const double DEFAULT_BEAT_LENGTH = 1000;
+
///
/// The beat length at this control point.
///
@@ -23,7 +25,7 @@ namespace osu.Game.Beatmaps.ControlPoints
set => beatLength = MathHelper.Clamp(value, 6, 60000);
}
- private double beatLength = 1000;
+ private double beatLength = DEFAULT_BEAT_LENGTH;
public bool Equals(TimingControlPoint other)
=> base.Equals(other)
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 3a4c677bd1..29ade24328 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
{
public override IEnumerable GetModsFor(ModType type) => new Mod[] { };
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods)
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods)
{
throw new NotImplementedException();
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs
index 540f616ea9..2c493254e0 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps.Formats
private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint
{
- public override double BeatLength { get; set; } = 1000;
+ public override double BeatLength { get; set; } = DEFAULT_BEAT_LENGTH;
}
}
}
diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs
index 512fe25809..8f27e0b0e9 100644
--- a/osu.Game/Beatmaps/IBeatmap.cs
+++ b/osu.Game/Beatmaps/IBeatmap.cs
@@ -53,4 +53,13 @@ namespace osu.Game.Beatmaps
/// The shallow-cloned beatmap.
IBeatmap Clone();
}
+
+ public interface IBeatmap : IBeatmap
+ where T : HitObject
+ {
+ ///
+ /// The hitobjects contained by this beatmap.
+ ///
+ new IReadOnlyList HitObjects { get; }
+ }
}
diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs
new file mode 100644
index 0000000000..aea3751bb5
--- /dev/null
+++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs
@@ -0,0 +1,61 @@
+// 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.Audio.Track;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
+using osu.Game.Storyboards;
+
+namespace osu.Game.Beatmaps
+{
+ public interface IWorkingBeatmap
+ {
+ ///
+ /// Retrieves the which this represents.
+ ///
+ IBeatmap Beatmap { get; }
+
+ ///
+ /// Retrieves the background for this .
+ ///
+ Texture Background { get; }
+
+ ///
+ /// Retrieves the audio track for this .
+ ///
+ Track Track { get; }
+
+ ///
+ /// Retrieves the for the of this .
+ ///
+ Waveform Waveform { get; }
+
+ ///
+ /// Retrieves the which this provides.
+ ///
+ Storyboard Storyboard { get; }
+
+ ///
+ /// Retrieves the which this provides.
+ ///
+ Skin Skin { get; }
+
+ ///
+ /// Constructs a playable from using the applicable converters for a specific .
+ ///
+ /// The returned is in a playable state - all and s
+ /// have been applied, and s have been fully constructed.
+ ///
+ ///
+ /// The to create a playable for.
+ /// The s to apply to the .
+ /// The converted .
+ /// If could not be converted to .
+ IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods);
+ }
+}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 8605caa5fe..90dde4239c 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -16,14 +16,13 @@ using osu.Framework.Audio;
using osu.Framework.Statistics;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Beatmaps
{
- public abstract class WorkingBeatmap : IDisposable
+ public abstract class WorkingBeatmap : IWorkingBeatmap, IDisposable
{
public readonly BeatmapInfo BeatmapInfo;
@@ -97,17 +96,6 @@ namespace osu.Game.Beatmaps
/// The applicable .
protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap);
- ///
- /// Constructs a playable from using the applicable converters for a specific .
- ///
- /// The returned is in a playable state - all and s
- /// have been applied, and s have been fully constructed.
- ///
- ///
- /// The to create a playable for.
- /// The s to apply to the .
- /// The converted .
- /// If could not be converted to .
public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods)
{
var rulesetInstance = ruleset.CreateInstance();
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index b13e115387..fb472f3f89 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Configuration
{
// UI/selection defaults
Set(OsuSetting.Ruleset, 0, 0, int.MaxValue);
- Set(OsuSetting.Skin, 0, 0, int.MaxValue);
+ Set(OsuSetting.Skin, 0, -1, int.MaxValue);
Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details);
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 0f7b26835b..9c948d6f90 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -62,15 +62,23 @@ namespace osu.Game.Graphics.Containers
protected override bool OnClick(ClickEvent e)
{
- if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
- {
- Hide();
- return true;
- }
+ closeIfOutside(e);
return base.OnClick(e);
}
+ protected override bool OnDragEnd(DragEndEvent e)
+ {
+ closeIfOutside(e);
+ return base.OnDragEnd(e);
+ }
+
+ private void closeIfOutside(MouseEvent e)
+ {
+ if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
+ Hide();
+ }
+
public virtual bool OnPressed(GlobalAction action)
{
switch (action)
diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs
index 125c994c92..533f02af7b 100644
--- a/osu.Game/Graphics/DrawableDate.cs
+++ b/osu.Game/Graphics/DrawableDate.cs
@@ -2,11 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.Sprites;
+using osu.Game.Utils;
namespace osu.Game.Graphics
{
@@ -71,7 +71,7 @@ namespace osu.Game.Graphics
Scheduler.AddDelayed(updateTimeWithReschedule, timeUntilNextUpdate);
}
- protected virtual string Format() => Date.Humanize();
+ protected virtual string Format() => HumanizerUtils.Humanize(Date);
private void updateTime() => Text = Format();
diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs
index 524a4742c0..f532302de2 100644
--- a/osu.Game/Graphics/ScreenshotManager.cs
+++ b/osu.Game/Graphics/ScreenshotManager.cs
@@ -22,7 +22,7 @@ using SixLabors.ImageSharp;
namespace osu.Game.Graphics
{
- public class ScreenshotManager : Container, IKeyBindingHandler, IHandleGlobalInput
+ public class ScreenshotManager : Container, IKeyBindingHandler, IHandleGlobalKeyboardInput
{
private readonly BindableBool cursorVisibility = new BindableBool(true);
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index b70072a222..bf758e21d9 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -10,7 +10,7 @@ using osu.Framework.Input.Bindings;
namespace osu.Game.Input.Bindings
{
- public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalInput
+ public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput
{
private readonly Drawable handler;
diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs
index cbc446a126..39ccf9fe1c 100644
--- a/osu.Game/Input/IdleTracker.cs
+++ b/osu.Game/Input/IdleTracker.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Input
///
/// Track whether the end-user is in an idle state, based on their last interaction with the game.
///
- public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalInput
+ public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalKeyboardInput
{
private readonly double timeToIdle;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 0e804ecbaf..8fa8ffaf9b 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -182,7 +182,26 @@ namespace osu.Game
// bind config int to database SkinInfo
configSkin = LocalConfig.GetBindable(OsuSetting.Skin);
SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID;
- configSkin.ValueChanged += skinId => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == skinId.NewValue) ?? SkinInfo.Default;
+ configSkin.ValueChanged += skinId =>
+ {
+ var skinInfo = SkinManager.Query(s => s.ID == skinId.NewValue);
+
+ if (skinInfo == null)
+ {
+ switch (skinId.NewValue)
+ {
+ case -1:
+ skinInfo = DefaultLegacySkin.Info;
+ break;
+
+ default:
+ skinInfo = SkinInfo.Default;
+ break;
+ }
+ }
+
+ SkinManager.CurrentSkinInfo.Value = skinInfo;
+ };
configSkin.TriggerChange();
IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true);
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 076c9ada78..de8f316b06 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -158,7 +158,7 @@ namespace osu.Game
runMigrations();
- dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio));
+ dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy")));
dependencies.CacheAs(SkinManager);
API = new APIAccess(LocalConfig);
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs
index ffc39e5af2..38a909411a 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.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 Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -14,6 +13,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Scoring;
using osu.Game.Users.Drawables;
+using osu.Game.Utils;
using osuTK;
using osuTK.Graphics;
@@ -132,7 +132,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
avatar.User = value.User;
flag.Country = value.User.Country;
- date.Text = $@"achieved {value.Date.Humanize()}";
+ date.Text = $@"achieved {HumanizerUtils.Humanize(value.Date)}";
usernameText.Clear();
usernameText.AddUserLink(value.User);
diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
index 26235fa280..9cd3aac2cb 100644
--- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
+++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
@@ -9,7 +9,7 @@ using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Volume
{
- public class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalInput
+ public class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalKeyboardInput
{
public Func ActionRequested;
public Func ScrollActionRequested;
diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs
deleted file mode 100644
index e85ebb5f3a..0000000000
--- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.UI;
-
-namespace osu.Game.Rulesets.Edit
-{
- public abstract class DrawableEditRuleset : CompositeDrawable
- {
- ///
- /// The contained by this .
- ///
- public abstract Playfield Playfield { get; }
-
- public abstract PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer();
-
- internal DrawableEditRuleset()
- {
- RelativeSizeAxes = Axes.Both;
- }
-
- ///
- /// Adds a to the and displays a visual representation of it.
- ///
- /// The to add.
- /// The visual representation of .
- internal abstract DrawableHitObject Add(HitObject hitObject);
-
- ///
- /// Removes a from the and the display.
- ///
- /// The to remove.
- /// The visual representation of the removed .
- internal abstract DrawableHitObject Remove(HitObject hitObject);
- }
-
- public class DrawableEditRuleset : DrawableEditRuleset
- where TObject : HitObject
- {
- public override Playfield Playfield => drawableRuleset.Playfield;
-
- public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer();
-
- private Ruleset ruleset => drawableRuleset.Ruleset;
- private Beatmap beatmap => drawableRuleset.Beatmap;
-
- private readonly DrawableRuleset drawableRuleset;
-
- public DrawableEditRuleset(DrawableRuleset drawableRuleset)
- {
- this.drawableRuleset = drawableRuleset;
-
- InternalChild = drawableRuleset;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- drawableRuleset.FrameStablePlayback = false;
- Playfield.DisplayJudgements.Value = false;
- }
-
- internal override DrawableHitObject Add(HitObject hitObject)
- {
- var tObject = (TObject)hitObject;
-
- // Add to beatmap, preserving sorting order
- var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime);
- beatmap.HitObjects.Insert(insertionIndex + 1, tObject);
-
- // Process object
- var processor = ruleset.CreateBeatmapProcessor(beatmap);
-
- processor?.PreProcess();
- tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
- processor?.PostProcess();
-
- // Add visual representation
- var drawableObject = drawableRuleset.CreateDrawableRepresentation(tObject);
-
- drawableRuleset.Playfield.Add(drawableObject);
- drawableRuleset.Playfield.PostProcess();
-
- return drawableObject;
- }
-
- internal override DrawableHitObject Remove(HitObject hitObject)
- {
- var tObject = (TObject)hitObject;
-
- // Remove from beatmap
- beatmap.HitObjects.Remove(tObject);
-
- // Process the beatmap
- var processor = ruleset.CreateBeatmapProcessor(beatmap);
-
- processor?.PreProcess();
- processor?.PostProcess();
-
- // Remove visual representation
- var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject);
-
- drawableRuleset.Playfield.Remove(drawableObject);
- drawableRuleset.Playfield.PostProcess();
-
- return drawableObject;
- }
- }
-}
diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs
new file mode 100644
index 0000000000..af565f8896
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs
@@ -0,0 +1,80 @@
+// 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;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Edit;
+
+namespace osu.Game.Rulesets.Edit
+{
+ ///
+ /// A wrapper for a . Handles adding visual representations of s to the underlying .
+ ///
+ internal class DrawableEditRulesetWrapper : CompositeDrawable
+ where TObject : HitObject
+ {
+ public Playfield Playfield => drawableRuleset.Playfield;
+
+ private readonly DrawableRuleset drawableRuleset;
+
+ [Resolved]
+ private IEditorBeatmap beatmap { get; set; }
+
+ public DrawableEditRulesetWrapper(DrawableRuleset drawableRuleset)
+ {
+ this.drawableRuleset = drawableRuleset;
+
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChild = drawableRuleset;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ drawableRuleset.FrameStablePlayback = false;
+ Playfield.DisplayJudgements.Value = false;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ beatmap.HitObjectAdded += addHitObject;
+ beatmap.HitObjectRemoved += removeHitObject;
+ }
+
+ private void addHitObject(HitObject hitObject)
+ {
+ var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject);
+
+ drawableRuleset.Playfield.Add(drawableObject);
+ drawableRuleset.Playfield.PostProcess();
+ }
+
+ private void removeHitObject(HitObject hitObject)
+ {
+ var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject);
+
+ drawableRuleset.Playfield.Remove(drawableObject);
+ drawableRuleset.Playfield.PostProcess();
+ }
+
+ public PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer();
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (beatmap != null)
+ {
+ beatmap.HitObjectAdded -= addHitObject;
+ beatmap.HitObjectRemoved -= removeHitObject;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 38ec09535d..fc324d7021 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -18,45 +18,47 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.RadioButtons;
+using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Edit
{
- public abstract class HitObjectComposer : CompositeDrawable
+ [Cached(Type = typeof(IPlacementHandler))]
+ public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler
+ where TObject : HitObject
{
- public IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects;
+ protected IRulesetConfigManager Config { get; private set; }
protected readonly Ruleset Ruleset;
- protected readonly IBindable Beatmap = new Bindable();
-
- protected IRulesetConfigManager Config { get; private set; }
-
- private readonly List layerContainers = new List();
-
- protected DrawableEditRuleset DrawableRuleset { get; private set; }
+ private IWorkingBeatmap workingBeatmap;
+ private Beatmap playableBeatmap;
+ private EditorBeatmap editorBeatmap;
+ private IBeatmapProcessor beatmapProcessor;
+ private DrawableEditRulesetWrapper drawableRulesetWrapper;
private BlueprintContainer blueprintContainer;
+ private readonly List layerContainers = new List();
private InputManager inputManager;
- internal HitObjectComposer(Ruleset ruleset)
+ protected HitObjectComposer(Ruleset ruleset)
{
Ruleset = ruleset;
-
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
- private void load(IBindable beatmap, IFrameBasedClock framedClock)
+ private void load(IFrameBasedClock framedClock)
{
- Beatmap.BindTo(beatmap);
-
try
{
- DrawableRuleset = CreateDrawableRuleset();
- DrawableRuleset.Clock = framedClock;
+ drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty()))
+ {
+ Clock = framedClock
+ };
}
catch (Exception e)
{
@@ -64,10 +66,10 @@ namespace osu.Game.Rulesets.Edit
return;
}
- var layerBelowRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer();
+ var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer();
layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both };
- var layerAboveRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer();
+ var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer();
layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer();
layerContainers.Add(layerBelowRuleset);
@@ -98,7 +100,7 @@ namespace osu.Game.Rulesets.Edit
Children = new Drawable[]
{
layerBelowRuleset,
- DrawableRuleset,
+ drawableRulesetWrapper,
layerAboveRuleset
}
}
@@ -118,6 +120,28 @@ namespace osu.Game.Rulesets.Edit
toolboxCollection.Items[0].Select();
}
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var parentWorkingBeatmap = parent.Get>().Value;
+
+ playableBeatmap = (Beatmap)parentWorkingBeatmap.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty());
+ workingBeatmap = new EditorWorkingBeatmap(playableBeatmap, parentWorkingBeatmap);
+
+ beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap);
+
+ editorBeatmap = new EditorBeatmap(playableBeatmap);
+ editorBeatmap.HitObjectAdded += addHitObject;
+ editorBeatmap.HitObjectRemoved += removeHitObject;
+
+ var dependencies = new DependencyContainer(parent);
+ dependencies.CacheAs(editorBeatmap);
+ dependencies.CacheAs>(editorBeatmap);
+
+ Config = dependencies.Get().GetConfigFor(Ruleset);
+
+ return base.CreateChildDependencies(dependencies);
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -125,45 +149,76 @@ namespace osu.Game.Rulesets.Edit
inputManager = GetContainingInputManager();
}
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
-
- dependencies.CacheAs(this);
- Config = dependencies.Get().GetConfigFor(Ruleset);
-
- return dependencies;
- }
-
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
layerContainers.ForEach(l =>
{
- l.Anchor = DrawableRuleset.Playfield.Anchor;
- l.Origin = DrawableRuleset.Playfield.Origin;
- l.Position = DrawableRuleset.Playfield.Position;
- l.Size = DrawableRuleset.Playfield.Size;
+ l.Anchor = drawableRulesetWrapper.Playfield.Anchor;
+ l.Origin = drawableRulesetWrapper.Playfield.Origin;
+ l.Position = drawableRulesetWrapper.Playfield.Position;
+ l.Size = drawableRulesetWrapper.Playfield.Size;
});
}
+ private void addHitObject(HitObject hitObject)
+ {
+ beatmapProcessor?.PreProcess();
+ hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty);
+ beatmapProcessor?.PostProcess();
+ }
+
+ private void removeHitObject(HitObject hitObject)
+ {
+ beatmapProcessor?.PreProcess();
+ beatmapProcessor?.PostProcess();
+ }
+
+ public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects;
+ public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position);
+
+ protected abstract IReadOnlyList CompositionTools { get; }
+
+ protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods);
+
+ public void BeginPlacement(HitObject hitObject)
+ {
+ }
+
+ public void EndPlacement(HitObject hitObject) => editorBeatmap.Add(hitObject);
+
+ public void Delete(HitObject hitObject) => editorBeatmap.Remove(hitObject);
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (editorBeatmap != null)
+ {
+ editorBeatmap.HitObjectAdded -= addHitObject;
+ editorBeatmap.HitObjectRemoved -= removeHitObject;
+ }
+ }
+ }
+
+ [Cached(typeof(HitObjectComposer))]
+ public abstract class HitObjectComposer : CompositeDrawable
+ {
+ internal HitObjectComposer()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ ///
+ /// All the s.
+ ///
+ public abstract IEnumerable HitObjects { get; }
+
///
/// Whether the user's cursor is currently in an area of the that is valid for placement.
///
- public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position);
-
- ///
- /// Adds a to the and visualises it.
- ///
- /// The to add.
- public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(DrawableRuleset.Add(hitObject));
-
- public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(DrawableRuleset.Remove(hitObject));
-
- internal abstract DrawableEditRuleset CreateDrawableRuleset();
-
- protected abstract IReadOnlyList CompositionTools { get; }
+ public abstract bool CursorInPlacementArea { get; }
///
/// Creates a for a specific .
@@ -176,18 +231,4 @@ namespace osu.Game.Rulesets.Edit
///
public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler();
}
-
- public abstract class HitObjectComposer : HitObjectComposer
- where TObject : HitObject
- {
- protected HitObjectComposer(Ruleset ruleset)
- : base(ruleset)
- {
- }
-
- internal override DrawableEditRuleset CreateDrawableRuleset()
- => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty()));
-
- protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods);
- }
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 80e70589bd..4a6f261905 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Game.Audio;
using osu.Game.Rulesets.Judgements;
@@ -186,6 +187,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Apply (generally fade-in) transforms leading into the start time.
/// The local drawable hierarchy is recursively delayed to for convenience.
+ ///
+ /// By default this will fade in the object from zero with no duration.
///
///
/// This is called once before every . This is to ensure a good state in the case
@@ -193,6 +196,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
protected virtual void UpdateInitialTransforms()
{
+ this.FadeInFromZero();
}
///
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 42b1322cae..b089840f1e 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets
/// The s to apply.
/// Unable to successfully load the beatmap to be usable with this ruleset.
///
- public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods);
+ public abstract DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods);
///
/// Creates a to convert a to one that is applicable for this .
diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
index 9bab065d1e..4b3c3f90f0 100644
--- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
+++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
@@ -20,7 +20,13 @@ namespace osu.Game.Rulesets.Timing
///
/// The aggregate multiplier which this provides.
///
- public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * 1000 / TimingPoint.BeatLength;
+ public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * BaseBeatLength / TimingPoint.BeatLength;
+
+ ///
+ /// The base beat length to scale the provided multiplier relative to.
+ ///
+ /// For a of 1000, a with a beat length of 500 will increase the multiplier by 2.
+ public double BaseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH;
///
/// The velocity multiplier.
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index ccfd89adca..021bd515b5 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.UI
/// The ruleset being represented.
/// The beatmap to create the hit renderer for.
/// The s to apply.
- protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IReadOnlyList mods)
+ protected DrawableRuleset(Ruleset ruleset, IWorkingBeatmap workingBeatmap, IReadOnlyList mods)
: base(ruleset)
{
if (workingBeatmap == null)
diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
index 42ec0b79b9..64e491858b 100644
--- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
@@ -69,6 +69,11 @@ namespace osu.Game.Rulesets.UI.Scrolling
///
protected virtual bool UserScrollSpeedAdjustment => true;
+ ///
+ /// Whether beat lengths should scale relative to the most common beat length in the .
+ ///
+ protected virtual bool RelativeScaleBeatLengths => false;
+
///
/// Provides the default s that adjust the scrolling rate of s
/// inside this .
@@ -81,7 +86,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
[Cached(Type = typeof(IScrollingInfo))]
private readonly LocalScrollingInfo scrollingInfo;
- protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ protected DrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
scrollingInfo = new LocalScrollingInfo();
@@ -107,16 +112,38 @@ namespace osu.Game.Rulesets.UI.Scrolling
[BackgroundDependencyLoader]
private void load()
{
- // Calculate default multiplier control points
+ double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
+ double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH;
+
+ if (RelativeScaleBeatLengths)
+ {
+ IReadOnlyList timingPoints = Beatmap.ControlPointInfo.TimingPoints;
+ double maxDuration = 0;
+
+ for (int i = 0; i < timingPoints.Count; i++)
+ {
+ if (timingPoints[i].Time > lastObjectTime)
+ break;
+
+ double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime;
+ double duration = endTime - timingPoints[i].Time;
+
+ if (duration > maxDuration)
+ {
+ maxDuration = duration;
+ baseBeatLength = timingPoints[i].BeatLength;
+ }
+ }
+ }
+
+ // Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point
var lastTimingPoint = new TimingControlPoint();
var lastDifficultyPoint = new DifficultyControlPoint();
-
- // Merge timing + difficulty points
var allPoints = new SortedList(Comparer.Default);
allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints);
allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints);
- // Generate the timing points, making non-timing changes use the previous timing change
+ // Generate the timing points, making non-timing changes use the previous timing change and vice-versa
var timingChanges = allPoints.Select(c =>
{
var timingPoint = c as TimingControlPoint;
@@ -131,14 +158,13 @@ namespace osu.Game.Rulesets.UI.Scrolling
return new MultiplierControlPoint(c.Time)
{
Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier,
+ BaseBeatLength = baseBeatLength,
TimingPoint = lastTimingPoint,
DifficultyPoint = lastDifficultyPoint
};
});
- double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
-
- // Perform some post processing of the timing changes
+ // Trim unwanted sequences of timing changes
timingChanges = timingChanges
// Collapse sections after the last hit object
.Where(s => s.StartTime <= lastObjectTime)
@@ -147,7 +173,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
controlPoints.AddRange(timingChanges);
- // If we have no control points, add a default one
if (controlPoints.Count == 0)
controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier });
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index a1e62cd38b..7d25fd5283 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -11,6 +11,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Input.States;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Screens.Edit.Compose.Components
@@ -29,6 +30,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
[Resolved]
private HitObjectComposer composer { get; set; }
+ [Resolved]
+ private IEditorBeatmap beatmap { get; set; }
+
public BlueprintContainer()
{
RelativeSizeAxes = Axes.Both;
@@ -53,7 +57,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
};
foreach (var obj in composer.HitObjects)
- AddBlueprintFor(obj);
+ addBlueprintFor(obj);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ beatmap.HitObjectAdded += addBlueprintFor;
+ beatmap.HitObjectRemoved += removeBlueprintFor;
}
private HitObjectCompositionTool currentTool;
@@ -75,11 +87,32 @@ namespace osu.Game.Screens.Edit.Compose.Components
}
}
- ///
- /// Adds a blueprint for a which adds movement support.
- ///
- /// The to create a blueprint for.
- public void AddBlueprintFor(DrawableHitObject hitObject)
+ private void addBlueprintFor(HitObject hitObject)
+ {
+ var drawable = composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject);
+ if (drawable == null)
+ return;
+
+ addBlueprintFor(drawable);
+ }
+
+ private void removeBlueprintFor(HitObject hitObject)
+ {
+ var blueprint = selectionBlueprints.Single(m => m.HitObject.HitObject == hitObject);
+ if (blueprint == null)
+ return;
+
+ blueprint.Deselect();
+
+ blueprint.Selected -= onBlueprintSelected;
+ blueprint.Deselected -= onBlueprintDeselected;
+ blueprint.SelectionRequested -= onSelectionRequested;
+ blueprint.DragRequested -= onDragRequested;
+
+ selectionBlueprints.Remove(blueprint);
+ }
+
+ private void addBlueprintFor(DrawableHitObject hitObject)
{
refreshTool();
@@ -95,25 +128,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
selectionBlueprints.Add(blueprint);
}
- ///
- /// Removes a blueprint for a .
- ///
- /// The for which to remove the blueprint.
- public void RemoveBlueprintFor(DrawableHitObject hitObject)
- {
- var blueprint = selectionBlueprints.Single(m => m.HitObject == hitObject);
- if (blueprint == null)
- return;
-
- blueprint.Deselect();
-
- blueprint.Selected -= onBlueprintSelected;
- blueprint.Deselected -= onBlueprintDeselected;
- blueprint.SelectionRequested -= onSelectionRequested;
- blueprint.DragRequested -= onDragRequested;
-
- selectionBlueprints.Remove(blueprint);
- }
+ private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject);
protected override bool OnClick(ClickEvent e)
{
@@ -183,6 +198,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent);
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (beatmap != null)
+ {
+ beatmap.HitObjectAdded -= addBlueprintFor;
+ beatmap.HitObjectRemoved -= removeBlueprintFor;
+ }
+ }
+
private class SelectionBlueprintContainer : Container
{
protected override int Compare(Drawable x, Drawable y)
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index 5699ef0a84..ec4dda5c23 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -9,15 +9,13 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose
{
- [Cached(Type = typeof(IPlacementHandler))]
- public class ComposeScreen : EditorScreen, IPlacementHandler
+ public class ComposeScreen : EditorScreen
{
private const float vertical_margins = 10;
private const float horizontal_margins = 20;
@@ -119,13 +117,5 @@ namespace osu.Game.Screens.Edit.Compose
composerContainer.Child = composer;
}
-
- public void BeginPlacement(HitObject hitObject)
- {
- }
-
- public void EndPlacement(HitObject hitObject) => composer.Add(hitObject);
-
- public void Delete(HitObject hitObject) => composer.Remove(hitObject);
}
}
diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs
new file mode 100644
index 0000000000..f0b6c62154
--- /dev/null
+++ b/osu.Game/Screens/Edit/EditorBeatmap.cs
@@ -0,0 +1,83 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Screens.Edit
+{
+ public class EditorBeatmap : IEditorBeatmap
+ where T : HitObject
+ {
+ public event Action HitObjectAdded;
+ public event Action HitObjectRemoved;
+
+ private readonly Beatmap beatmap;
+
+ public EditorBeatmap(Beatmap beatmap)
+ {
+ this.beatmap = beatmap;
+ }
+
+ public BeatmapInfo BeatmapInfo
+ {
+ get => beatmap.BeatmapInfo;
+ set => beatmap.BeatmapInfo = value;
+ }
+
+ public BeatmapMetadata Metadata => beatmap.Metadata;
+
+ public ControlPointInfo ControlPointInfo => beatmap.ControlPointInfo;
+
+ public List Breaks => beatmap.Breaks;
+
+ public double TotalBreakTime => beatmap.TotalBreakTime;
+
+ IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects;
+
+ IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects;
+
+ public IEnumerable GetStatistics() => beatmap.GetStatistics();
+
+ public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone();
+
+ ///
+ /// Adds a to this .
+ ///
+ /// The to add.
+ public void Add(T hitObject)
+ {
+ // Preserve existing sorting order in the beatmap
+ var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime);
+ beatmap.HitObjects.Insert(insertionIndex + 1, hitObject);
+
+ HitObjectAdded?.Invoke(hitObject);
+ }
+
+ ///
+ /// Removes a from this .
+ ///
+ /// The to add.
+ public void Remove(T hitObject)
+ {
+ if (beatmap.HitObjects.Remove(hitObject))
+ HitObjectRemoved?.Invoke(hitObject);
+ }
+
+ ///
+ /// Adds a to this .
+ ///
+ /// The to add.
+ public void Add(HitObject hitObject) => Add((T)hitObject);
+
+ ///
+ /// Removes a from this .
+ ///
+ /// The to add.
+ public void Remove(HitObject hitObject) => Remove((T)hitObject);
+ }
+}
diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs
new file mode 100644
index 0000000000..45fca493a2
--- /dev/null
+++ b/osu.Game/Screens/Edit/EditorWorkingBeatmap.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.Collections.Generic;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Skinning;
+using osu.Game.Storyboards;
+
+namespace osu.Game.Screens.Edit
+{
+ ///
+ /// Encapsulates a while providing an overridden .
+ ///
+ ///
+ public class EditorWorkingBeatmap : IWorkingBeatmap
+ where TObject : HitObject
+ {
+ private readonly Beatmap playableBeatmap;
+ private readonly WorkingBeatmap workingBeatmap;
+
+ public EditorWorkingBeatmap(Beatmap playableBeatmap, WorkingBeatmap workingBeatmap)
+ {
+ this.playableBeatmap = playableBeatmap;
+ this.workingBeatmap = workingBeatmap;
+ }
+
+ public IBeatmap Beatmap => workingBeatmap.Beatmap;
+
+ public Texture Background => workingBeatmap.Background;
+
+ public Track Track => workingBeatmap.Track;
+
+ public Waveform Waveform => workingBeatmap.Waveform;
+
+ public Storyboard Storyboard => workingBeatmap.Storyboard;
+
+ public Skin Skin => workingBeatmap.Skin;
+
+ public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) => playableBeatmap;
+ }
+}
diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs
new file mode 100644
index 0000000000..2f250ba446
--- /dev/null
+++ b/osu.Game/Screens/Edit/IEditorBeatmap.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Screens.Edit
+{
+ ///
+ /// Interface for the contained by the see .
+ /// Children of may resolve the beatmap via or .
+ ///
+ public interface IEditorBeatmap : IBeatmap
+ {
+ ///
+ /// Invoked when a is added to this .
+ ///
+ event Action HitObjectAdded;
+
+ ///
+ /// Invoked when a is removed from this .
+ ///
+ event Action HitObjectRemoved;
+ }
+
+ ///
+ /// Interface for the contained by the see .
+ /// Children of may resolve the beatmap via or .
+ ///
+ public interface IEditorBeatmap : IEditorBeatmap, IBeatmap
+ where T : HitObject
+ {
+ }
+}
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
new file mode 100644
index 0000000000..b35c9c7b97
--- /dev/null
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -0,0 +1,23 @@
+// 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;
+using osu.Framework.IO.Stores;
+
+namespace osu.Game.Skinning
+{
+ public class DefaultLegacySkin : LegacySkin
+ {
+ public DefaultLegacySkin(IResourceStore storage, AudioManager audioManager)
+ : base(Info, storage, audioManager, string.Empty)
+ {
+ }
+
+ public static SkinInfo Info { get; } = new SkinInfo
+ {
+ ID = -1, // this is temporary until database storage is decided upon.
+ Name = "osu!classic",
+ Creator = "team osu!"
+ };
+ }
+}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 570ba1ced7..0cc5e9c9b6 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -248,6 +248,9 @@ namespace osu.Game.Skinning
private string getPathForFile(string filename)
{
+ if (source.Files == null)
+ return null;
+
bool hasExtension = filename.Contains('.');
var file = source.Files.Find(f =>
diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs
index 187ea910a7..6b9627188e 100644
--- a/osu.Game/Skinning/SkinInfo.cs
+++ b/osu.Game/Skinning/SkinInfo.cs
@@ -26,7 +26,11 @@ namespace osu.Game.Skinning
public string FullName => $"\"{Name}\" by {Creator}";
- public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" };
+ public static SkinInfo Default { get; } = new SkinInfo
+ {
+ Name = "osu!lazer",
+ Creator = "team osu!"
+ };
public bool Equals(SkinInfo other) => other != null && ID == other.ID;
diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs
index e747a8b1ce..a713933c6e 100644
--- a/osu.Game/Skinning/SkinManager.cs
+++ b/osu.Game/Skinning/SkinManager.cs
@@ -14,6 +14,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Audio;
using osu.Game.Database;
@@ -25,6 +26,8 @@ namespace osu.Game.Skinning
{
private readonly AudioManager audio;
+ private readonly IResourceStore legacyDefaultResources;
+
public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin());
public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default };
@@ -34,10 +37,11 @@ namespace osu.Game.Skinning
protected override string ImportFromStablePath => "Skins";
- public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio)
+ public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio, IResourceStore legacyDefaultResources)
: base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost)
{
this.audio = audio;
+ this.legacyDefaultResources = legacyDefaultResources;
ItemRemoved += removedInfo =>
{
@@ -66,6 +70,7 @@ namespace osu.Game.Skinning
{
var userSkins = GetAllUserSkins();
userSkins.Insert(0, SkinInfo.Default);
+ userSkins.Insert(1, DefaultLegacySkin.Info);
return userSkins;
}
@@ -91,7 +96,7 @@ namespace osu.Game.Skinning
else
{
model.Name = model.Name.Replace(".osk", "");
- model.Creator = "Unknown";
+ model.Creator = model.Creator ?? "Unknown";
}
}
@@ -105,6 +110,9 @@ namespace osu.Game.Skinning
if (skinInfo == SkinInfo.Default)
return new DefaultSkin();
+ if (skinInfo == DefaultLegacySkin.Info)
+ return new DefaultLegacySkin(legacyDefaultResources, audio);
+
return new LegacySkin(skinInfo, Files.Store, audio);
}
diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs
new file mode 100644
index 0000000000..5b7c3630d9
--- /dev/null
+++ b/osu.Game/Utils/HumanizerUtils.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Globalization;
+using Humanizer;
+
+namespace osu.Game.Utils
+{
+ public static class HumanizerUtils
+ {
+ ///
+ /// Turns the current or provided date into a human readable sentence
+ ///
+ /// The date to be humanized
+ /// distance of time in words
+ public static string Humanize(DateTimeOffset input)
+ {
+ // this works around https://github.com/xamarin/xamarin-android/issues/2012 and https://github.com/Humanizr/Humanizer/issues/690#issuecomment-368536282
+ try
+ {
+ return input.Humanize();
+ }
+ catch (ArgumentException)
+ {
+ return input.Humanize(culture: new CultureInfo("en-US"));
+ }
+ }
+ }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 4fe9119cef..8e6ce2d1ba 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -14,8 +14,8 @@
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 82301549d7..47cc6ec97a 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -117,9 +117,9 @@
-
-
-
+
+
+