diff --git a/osu.Android.props b/osu.Android.props
index fe6420ead8..2c3c8bcaad 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -60,7 +60,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 33f93cdb4a..406c0af28d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -15,6 +15,7 @@ using osu.Framework.Graphics.Sprites;
using osuTK.Graphics;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -92,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Tests
return null;
}
- public SampleChannel GetSample(string sampleName) =>
+ public SampleChannel GetSample(ISampleInfo sampleInfo) =>
throw new NotImplementedException();
public Texture GetTexture(string componentName) =>
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 4100404da6..7c282f449b 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -4,7 +4,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 a1279e8443..00734810b3 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -58,14 +58,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss);
}
- protected override bool UseTransformStateManagement => false;
+ protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateInitialTransforms() => this.FadeInFromZero(200);
+
+ protected override void UpdateStateTransforms(ArmedState state)
{
- // TODO: update to use new state management.
- using (BeginAbsoluteSequence(HitObject.StartTime - HitObject.TimePreempt))
- this.FadeIn(200);
-
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
using (BeginAbsoluteSequence(endTime, true))
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 62abe53559..ceda643335 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (lastPlateableFruit == null)
return;
- // this is required to make this run after the last caught fruit runs UpdateState at least once.
+ // this is required to make this run after the last caught fruit runs updateState() at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
action();
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.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index 013d2a71d4..4dcfc1b81f 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -4,7 +4,7 @@
-
+
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/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index 9c3197504f..e9c352c97e 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Alpha = 0.2f;
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 952c6e128e..fc3b6885d7 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
@@ -104,6 +105,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
}
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ using (BeginDelayedSequence(HitObject.Duration, true))
+ base.UpdateStateTransforms(state);
+ }
+
protected void BeginHold()
{
holdStartTime = Time.Current;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index db6b53e76d..e5b114ca81 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -45,24 +45,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
- }
- public abstract class DrawableManiaHitObject : DrawableManiaHitObject
- where TObject : ManiaHitObject
- {
- public new readonly TObject HitObject;
-
- protected DrawableManiaHitObject(TObject hitObject)
- : base(hitObject)
+ protected override void UpdateStateTransforms(ArmedState state)
{
- HitObject = hitObject;
- }
-
- protected override bool UseTransformStateManagement => false;
-
- protected override void UpdateState(ArmedState state)
- {
- // TODO: update to use new state management.
switch (state)
{
case ArmedState.Miss:
@@ -75,4 +60,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
}
+
+ public abstract class DrawableManiaHitObject : DrawableManiaHitObject
+ where TObject : ManiaHitObject
+ {
+ public new readonly TObject HitObject;
+
+ protected DrawableManiaHitObject(TObject hitObject)
+ : base(hitObject)
+ {
+ HitObject = hitObject;
+ }
+ }
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index e5669816fa..7b8bbc2095 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
@@ -77,13 +78,37 @@ namespace osu.Game.Rulesets.Mania.Replays
private IEnumerable generateActionPoints()
{
- foreach (var obj in Beatmap.HitObjects)
+ for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
- yield return new HitPoint { Time = obj.StartTime, Column = obj.Column };
- yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column };
+ var currentObject = Beatmap.HitObjects[i];
+ var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button
+
+ double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime;
+
+ bool canDelayKeyUp = nextObjectInColumn == null ||
+ nextObjectInColumn.StartTime > endTime + RELEASE_DELAY;
+
+ double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInColumn.StartTime - endTime) * 0.9;
+
+ yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column };
+
+ yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column };
}
}
+ protected override HitObject GetNextObject(int currentIndex)
+ {
+ int desiredColumn = Beatmap.HitObjects[currentIndex].Column;
+
+ for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
+ {
+ if (Beatmap.HitObjects[i].Column == desiredColumn)
+ return Beatmap.HitObjects[i];
+ }
+
+ return null;
+ }
+
private interface IActionPoint
{
double Time { get; set; }
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.Tests/Resources/metrics-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png
new file mode 100644
index 0000000000..72ef665478
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index 92c5c77aac..197309c7c4 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -4,7 +4,7 @@
-
+
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.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
index 6f9856df83..3c84d900a6 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
@@ -247,10 +247,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
: base(hitObject)
{
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 82055ecaee..a5db1625d9 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
index f8909fb98c..bf89f7e15b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
@@ -53,9 +53,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Alpha = 0.75f
});
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index 9b4df74a61..f4407a7b54 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -88,13 +88,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = HitResult.Miss);
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
switch (state)
{
case ArmedState.Hit:
case ArmedState.Miss:
- this.FadeOut(100).Expire();
+ this.Delay(HitObject.Duration).FadeOut(100).Expire();
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index 9259c693d9..cef9a53deb 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = HitResult.Great);
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
switch (state)
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 34ae7db984..fa45067210 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -92,56 +92,42 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Size = BaseSize * Parent.RelativeChildSize;
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
- // TODO: update to use new state management.
- var circlePiece = MainPiece as CirclePiece;
- circlePiece?.FlashBox.FinishTransforms();
-
- var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime;
-
- using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true))
+ switch (state)
{
- switch (State.Value)
- {
- case ArmedState.Idle:
- validActionPressed = false;
+ case ArmedState.Idle:
+ validActionPressed = false;
- UnproxyContent();
- this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
- break;
+ UnproxyContent();
+ this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
+ break;
- case ArmedState.Miss:
- this.FadeOut(100)
- .Expire();
- break;
+ case ArmedState.Miss:
+ this.FadeOut(100)
+ .Expire();
+ break;
- case ArmedState.Hit:
- // If we're far enough away from the left stage, we should bring outselves in front of it
- ProxyContent();
+ case ArmedState.Hit:
+ // If we're far enough away from the left stage, we should bring outselves in front of it
+ ProxyContent();
- var flash = circlePiece?.FlashBox;
+ var flash = (MainPiece as CirclePiece)?.FlashBox;
+ flash?.FadeTo(0.9f).FadeOut(300);
- if (flash != null)
- {
- flash.FadeTo(0.9f);
- flash.FadeOut(300);
- }
+ const float gravity_time = 300;
+ const float gravity_travel_height = 200;
- const float gravity_time = 300;
- const float gravity_travel_height = 200;
+ this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
- this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
+ this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
+ .Then()
+ .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In);
- this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
- .Then()
- .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In);
+ this.FadeOut(800)
+ .Expire();
- this.FadeOut(800)
- .Expire();
-
- break;
- }
+ break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs
index 98a2e8a721..108e42eea5 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs
@@ -18,9 +18,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
MainObject = mainObject;
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 82448ec7d5..094ad1230f 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -25,6 +25,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private const float target_ring_scale = 5f;
private const float inner_ring_alpha = 0.65f;
+ ///
+ /// Offset away from the start time of the swell at which the ring starts appearing.
+ ///
+ private const double ring_appear_offset = 100;
+
private readonly List ticks = new List();
private readonly Container bodyContainer;
@@ -179,26 +184,34 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateInitialTransforms()
{
- const float preempt = 100;
- const float out_transition_time = 300;
+ base.UpdateInitialTransforms();
+
+ using (BeginAbsoluteSequence(HitObject.StartTime - ring_appear_offset, true))
+ targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint);
+ }
+
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ const double transition_duration = 300;
switch (state)
{
case ArmedState.Idle:
- UnproxyContent();
expandingRing.FadeTo(0);
- using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true))
- targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
break;
case ArmedState.Miss:
case ArmedState.Hit:
- this.FadeOut(out_transition_time, Easing.Out);
- bodyContainer.ScaleTo(1.4f, out_transition_time);
+ using (BeginAbsoluteSequence(Time.Current, true))
+ {
+ this.FadeOut(transition_duration, Easing.Out);
+ bodyContainer.ScaleTo(1.4f, transition_duration);
+
+ Expire();
+ }
- Expire();
break;
}
}
@@ -212,9 +225,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Make the swell stop at the hit target
X = Math.Max(0, X);
- double t = Math.Min(HitObject.StartTime, Time.Current);
- if (t == HitObject.StartTime)
+ if (Time.Current >= HitObject.StartTime - ring_appear_offset)
ProxyContent();
+ else
+ UnproxyContent();
}
private bool? lastWasCentre;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
index 41a8fd9a75..8b27d78101 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@@ -21,10 +20,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
}
- protected override void UpdateState(ArmedState state)
- {
- }
-
public override bool OnPressed(TaikoAction action) => false;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index b46738c69a..5424ccb4de 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -78,10 +78,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public abstract bool OnPressed(TaikoAction action);
public virtual bool OnReleased(TaikoAction action) => false;
+ public override double LifetimeStart
+ {
+ get => base.LifetimeStart;
+ set
+ {
+ base.LifetimeStart = value;
+ proxiedContent.LifetimeStart = value;
+ }
+ }
+
+ public override double LifetimeEnd
+ {
+ get => base.LifetimeEnd;
+ set
+ {
+ base.LifetimeEnd = value;
+ proxiedContent.LifetimeEnd = value;
+ }
+ }
+
private class ProxiedContentContainer : Container
{
- public override double LifetimeStart => Parent?.LifetimeStart ?? base.LifetimeStart;
- public override double LifetimeEnd => Parent?.LifetimeEnd ?? base.LifetimeEnd;
+ public override bool RemoveWhenNotAlive => false;
}
}
@@ -121,8 +140,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
- protected override bool UseTransformStateManagement => false;
-
// Normal and clap samples are handled by the drum
protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index 422ba748e3..299679b2c1 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Taiko.Replays
{
@@ -113,7 +114,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
else
throw new InvalidOperationException("Unknown hit object type.");
- Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY));
+ var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
+
+ bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY;
+
+ double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9;
+
+ Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay));
if (i < Beatmap.HitObjects.Count - 1)
{
@@ -127,5 +134,24 @@ namespace osu.Game.Rulesets.Taiko.Replays
return Replay;
}
+
+ protected override HitObject GetNextObject(int currentIndex)
+ {
+ Type desiredType = Beatmap.HitObjects[currentIndex].GetType();
+
+ for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
+ {
+ var currentObj = Beatmap.HitObjects[i];
+
+ if (currentObj.GetType() == desiredType ||
+ // Un-press all keys before a DrumRoll or Swell
+ currentObj is DrumRoll || currentObj is Swell)
+ {
+ return Beatmap.HitObjects[i];
+ }
+ }
+
+ return null;
+ }
}
}
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/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 971518909d..953763c95d 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
- int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample));
+ int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo));
Assert.AreEqual(15, spriteCount);
Assert.AreEqual(1, animationCount);
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.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index 0a9cdc6a8e..aa80819694 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -200,10 +200,6 @@ namespace osu.Game.Tests.Visual.Gameplay
break;
}
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
private class TestDrawableHitObject : DrawableHitObject
@@ -216,10 +212,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddInternal(new Box { Size = new Vector2(75) });
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index 0b5978e3eb..6c003e62ec 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Skinning;
@@ -253,7 +254,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName) => throw new NotImplementedException();
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
}
@@ -264,7 +265,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName) => throw new NotImplementedException();
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
}
@@ -275,7 +276,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName) => throw new NotImplementedException();
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index daee419b52..8f19df65a9 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -135,6 +135,9 @@ namespace osu.Game.Tests.Visual.Online
});
downloadAssert(true);
+
+ AddStep("show many difficulties", () => overlay.ShowBeatmapSet(createManyDifficultiesBeatmapSet()));
+ downloadAssert(true);
}
[Test]
@@ -222,6 +225,56 @@ namespace osu.Game.Tests.Visual.Online
AddStep(@"show without reload", overlay.Show);
}
+ private BeatmapSetInfo createManyDifficultiesBeatmapSet()
+ {
+ var beatmaps = new List();
+
+ for (int i = 1; i < 41; i++)
+ {
+ beatmaps.Add(new BeatmapInfo
+ {
+ OnlineBeatmapID = i * 10,
+ Version = $"Test #{i}",
+ Ruleset = Ruleset.Value,
+ StarDifficulty = 2 + i * 0.1,
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ OverallDifficulty = 3.5f,
+ },
+ OnlineInfo = new BeatmapOnlineInfo(),
+ Metrics = new BeatmapMetrics
+ {
+ Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
+ },
+ });
+ }
+
+ return new BeatmapSetInfo
+ {
+ OnlineBeatmapSetID = 123,
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"many difficulties beatmap",
+ Artist = @"none",
+ Author = new User
+ {
+ Username = @"BanchoBot",
+ Id = 3,
+ },
+ },
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Preview = @"https://b.ppy.sh/preview/123.mp3",
+ HasVideo = true,
+ HasStoryboard = true,
+ Covers = new BeatmapSetOnlineCovers(),
+ },
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
+ Beatmaps = beatmaps,
+ };
+ }
+
private void downloadAssert(bool shown)
{
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.DownloadButtonsVisible == shown);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
index 53dbaeddda..731cb62518 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Osu;
using osu.Game.Users;
using osuTK;
@@ -24,7 +23,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(IconPill)
};
- private BeatmapSetInfo getUndownloadableBeatmapSet(RulesetInfo ruleset) => new BeatmapSetInfo
+ private BeatmapSetInfo getUndownloadableBeatmapSet() => new BeatmapSetInfo
{
OnlineBeatmapSetID = 123,
Metadata = new BeatmapMetadata
@@ -56,23 +55,62 @@ namespace osu.Game.Tests.Visual.Online
{
new BeatmapInfo
{
- Ruleset = ruleset,
+ Ruleset = Ruleset.Value,
Version = "Test",
StarDifficulty = 6.42,
}
}
};
- [BackgroundDependencyLoader]
- private void load()
+ private BeatmapSetInfo getManyDifficultiesBeatmapSet(RulesetStore rulesets)
{
- var ruleset = new OsuRuleset().RulesetInfo;
+ var beatmaps = new List();
- var normal = CreateWorkingBeatmap(ruleset).BeatmapSetInfo;
+ for (int i = 0; i < 100; i++)
+ {
+ beatmaps.Add(new BeatmapInfo
+ {
+ Ruleset = rulesets.GetRuleset(i % 4),
+ StarDifficulty = 2 + i % 4 * 2,
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ OverallDifficulty = 3.5f,
+ }
+ });
+ }
+
+ return new BeatmapSetInfo
+ {
+ OnlineBeatmapSetID = 1,
+ Metadata = new BeatmapMetadata
+ {
+ Title = "many difficulties beatmap",
+ Artist = "test",
+ Author = new User
+ {
+ Username = "BanchoBot",
+ Id = 3,
+ }
+ },
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ HasVideo = true,
+ HasStoryboard = true,
+ Covers = new BeatmapSetOnlineCovers(),
+ },
+ Beatmaps = beatmaps,
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ var normal = CreateWorkingBeatmap(Ruleset.Value).BeatmapSetInfo;
normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true;
- var undownloadable = getUndownloadableBeatmapSet(ruleset);
+ var undownloadable = getUndownloadableBeatmapSet();
+ var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
Child = new BasicScrollContainer
{
@@ -81,15 +119,17 @@ namespace osu.Game.Tests.Visual.Online
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
+ Direction = FillDirection.Full,
Padding = new MarginPadding(20),
- Spacing = new Vector2(0, 20),
+ Spacing = new Vector2(5, 20),
Children = new Drawable[]
{
new DirectGridPanel(normal),
- new DirectListPanel(normal),
new DirectGridPanel(undownloadable),
+ new DirectGridPanel(manyDifficulties),
+ new DirectListPanel(normal),
new DirectListPanel(undownloadable),
+ new DirectListPanel(manyDifficulties),
},
},
};
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 7c9b7c7815..6669ec7da3 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -516,6 +516,7 @@ namespace osu.Game.Tests.Visual.SongSelect
OnlineBeatmapID = b * 10,
Path = $"extra{b}.osu",
Version = $"Extra {b}",
+ Ruleset = rulesets.GetRuleset((b - 1) % 4),
StarDifficulty = 2,
BaseDifficulty = new BeatmapDifficulty
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 680250a226..263eada07c 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -15,6 +15,7 @@ using osu.Framework.MathUtils;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@@ -79,8 +80,12 @@ namespace osu.Game.Tests.Visual.SongSelect
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default));
Beatmap.SetDefault();
+
+ Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
}
+ private OsuConfigManager config;
+
[SetUp]
public virtual void SetUp() => Schedule(() =>
{
@@ -111,13 +116,15 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
- AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; });
- AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; });
- AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; });
- AddStep(@"Sort by DateAdded", delegate { songSelect.FilterControl.Sort = SortMode.DateAdded; });
- AddStep(@"Sort by BPM", delegate { songSelect.FilterControl.Sort = SortMode.BPM; });
- AddStep(@"Sort by Length", delegate { songSelect.FilterControl.Sort = SortMode.Length; });
- AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; });
+ var sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode);
+
+ AddStep(@"Sort by Artist", delegate { sortMode.Value = SortMode.Artist; });
+ AddStep(@"Sort by Title", delegate { sortMode.Value = SortMode.Title; });
+ AddStep(@"Sort by Author", delegate { sortMode.Value = SortMode.Author; });
+ AddStep(@"Sort by DateAdded", delegate { sortMode.Value = SortMode.DateAdded; });
+ AddStep(@"Sort by BPM", delegate { sortMode.Value = SortMode.BPM; });
+ AddStep(@"Sort by Length", delegate { sortMode.Value = SortMode.Length; });
+ AddStep(@"Sort by Difficulty", delegate { sortMode.Value = SortMode.Difficulty; });
}
[Test]
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 50530088c2..4a9d88f3a6 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index 257db89a20..2a8bd393da 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -7,7 +7,7 @@
-
+
WinExe
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/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 5657b8fb8a..2d8a0b1249 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -138,19 +138,15 @@ namespace osu.Game.Beatmaps
protected override Skin GetSkin()
{
- Skin skin;
-
try
{
- skin = new LegacyBeatmapSkin(BeatmapInfo, store, AudioManager);
+ return new LegacyBeatmapSkin(BeatmapInfo, store, AudioManager);
}
catch (Exception e)
{
Logger.Error(e, "Skin failed to load");
- skin = new DefaultSkin();
+ return null;
}
-
- return skin;
}
}
}
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/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 5cce29d609..81f517dd86 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -19,23 +19,33 @@ using osuTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
- public class DifficultyIcon : Container, IHasCustomTooltip
+ public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip
{
private readonly BeatmapInfo beatmap;
private readonly RulesetInfo ruleset;
+ private readonly Container iconContainer;
+
+ ///
+ /// Size of this difficulty icon.
+ ///
+ public new Vector2 Size
+ {
+ get => iconContainer.Size;
+ set => iconContainer.Size = value;
+ }
+
public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true)
{
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
-
- this.beatmap = beatmap;
+ this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap));
this.ruleset = ruleset ?? beatmap.Ruleset;
if (shouldShowTooltip)
TooltipContent = beatmap;
- Size = new Vector2(20);
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = iconContainer = new Container { Size = new Vector2(20f) };
}
public string TooltipText { get; set; }
@@ -47,7 +57,7 @@ namespace osu.Game.Beatmaps.Drawables
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- Children = new Drawable[]
+ iconContainer.Children = new Drawable[]
{
new CircularContainer
{
diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs
new file mode 100644
index 0000000000..fbad113caa
--- /dev/null
+++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets;
+using osuTK.Graphics;
+
+namespace osu.Game.Beatmaps.Drawables
+{
+ ///
+ /// A difficulty icon that contains a counter on the right-side of it.
+ ///
+ ///
+ /// Used in cases when there are too many difficulty icons to show.
+ ///
+ public class GroupedDifficultyIcon : DifficultyIcon
+ {
+ public GroupedDifficultyIcon(List beatmaps, RulesetInfo ruleset, Color4 counterColour)
+ : base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, false)
+ {
+ AddInternal(new OsuSpriteText
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Padding = new MarginPadding { Left = Size.X },
+ Margin = new MarginPadding { Left = 2, Right = 5 },
+ Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
+ Text = beatmaps.Count.ToString(),
+ Colour = counterColour,
+ });
+ }
+ }
+}
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/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index 17df9ccc7e..14c6ea5c8e 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -121,7 +121,7 @@ namespace osu.Game.Beatmaps.Formats
var layer = parseLayer(split[2]);
var path = cleanFilename(split[3]);
var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
- storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
+ storyboard.GetLayer(layer).Add(new StoryboardSampleInfo(path, time, (int)volume));
break;
}
}
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 fbb17fa7f9..0cecbb225f 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -8,6 +8,7 @@ using osu.Framework.Platform;
using osu.Game.Overlays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select;
+using osu.Game.Screens.Select.Filter;
namespace osu.Game.Configuration
{
@@ -17,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);
@@ -25,6 +26,9 @@ namespace osu.Game.Configuration
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1);
+ Set(OsuSetting.SongSelectGroupingMode, GroupMode.All);
+ Set(OsuSetting.SongSelectSortingMode, SortMode.Title);
+
Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
@@ -152,6 +156,8 @@ namespace osu.Game.Configuration
SaveUsername,
DisplayStarsMinimum,
DisplayStarsMaximum,
+ SongSelectGroupingMode,
+ SongSelectSortingMode,
RandomSelectAlgorithm,
ShowFpsDisplay,
ChatDisplayHeight,
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 5606328575..9c948d6f90 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -15,6 +15,7 @@ using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers
{
+ [Cached(typeof(IPreviewTrackOwner))]
public abstract class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler
{
private SampleChannel samplePopIn;
@@ -38,13 +39,6 @@ namespace osu.Game.Graphics.Containers
protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All);
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- dependencies.CacheAs(this);
- return dependencies;
- }
-
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio)
{
@@ -68,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/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
index 104315f1c2..28947b6f22 100644
--- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
+++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
@@ -91,7 +91,8 @@ namespace osu.Game.Overlays.BeatmapSet
{
difficulties = new DifficultiesContainer
{
- AutoSizeAxes = Axes.Both,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2) },
OnLostHover = () =>
{
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/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 243e79eb9b..7bf94c1483 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Direct
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
- Children = GetDifficultyIcons(),
+ Children = GetDifficultyIcons(colours),
},
},
},
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 5757e1445b..158ff648dd 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -129,7 +129,7 @@ namespace osu.Game.Overlays.Direct
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
- Children = GetDifficultyIcons(),
+ Children = GetDifficultyIcons(colours),
},
},
},
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 8199d80528..641423f21f 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Direct
public readonly BeatmapSetInfo SetInfo;
private const double hover_transition_time = 400;
+ private const int maximum_difficulty_icons = 15;
private Container content;
@@ -138,12 +139,18 @@ namespace osu.Game.Overlays.Direct
};
}
- protected List GetDifficultyIcons()
+ protected List GetDifficultyIcons(OsuColour colours)
{
var icons = new List();
- foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty))
- icons.Add(new DifficultyIcon(b));
+ if (SetInfo.Beatmaps.Count > maximum_difficulty_icons)
+ {
+ foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct())
+ icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : colours.Gray5));
+ }
+ else
+ foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty))
+ icons.Add(new DifficultyIcon(b));
return icons;
}
diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
index 8a6b52b7ee..919f8a2fa0 100644
--- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
@@ -1,21 +1,22 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
+using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Direct;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Overlays.Profile.Sections.Beatmaps
{
- public class PaginatedBeatmapContainer : PaginatedContainer
+ public class PaginatedBeatmapContainer : PaginatedContainer
{
private const float panel_padding = 10f;
private readonly BeatmapSetType type;
- private GetUserBeatmapsRequest request;
public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.")
: base(user, header, missing)
@@ -27,40 +28,15 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
ItemsContainer.Spacing = new Vector2(panel_padding);
}
- protected override void ShowMore()
- {
- request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
- request.Success += sets => Schedule(() =>
+ protected override APIRequest> CreateRequest() =>
+ new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
+
+ protected override Drawable CreateDrawableItem(APIBeatmapSet model) => !model.OnlineBeatmapSetID.HasValue
+ ? null
+ : new DirectGridPanel(model.ToBeatmapSet(Rulesets))
{
- MoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0);
- MoreButton.IsLoading = false;
-
- if (!sets.Any() && VisiblePages == 1)
- {
- MissingText.Show();
- return;
- }
-
- foreach (var s in sets)
- {
- if (!s.OnlineBeatmapSetID.HasValue)
- continue;
-
- ItemsContainer.Add(new DirectGridPanel(s.ToBeatmapSet(Rulesets))
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- });
- }
- });
-
- Api.Queue(request);
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- request?.Cancel();
- }
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ };
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs
index 23072f8d90..6e6d6272c7 100644
--- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs
@@ -1,19 +1,19 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
+using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
namespace osu.Game.Overlays.Profile.Sections.Historical
{
- public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer
+ public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer
{
- private GetUserMostPlayedBeatmapsRequest request;
-
public PaginatedMostPlayedBeatmapContainer(Bindable user)
: base(user, "Most Played Beatmaps", "No records. :(")
{
@@ -22,35 +22,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
ItemsContainer.Direction = FillDirection.Vertical;
}
- protected override void ShowMore()
- {
- request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage);
- request.Success += beatmaps => Schedule(() =>
- {
- MoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0);
- MoreButton.IsLoading = false;
+ protected override APIRequest> CreateRequest() =>
+ new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage);
- if (!beatmaps.Any() && VisiblePages == 1)
- {
- MissingText.Show();
- return;
- }
-
- MissingText.Hide();
-
- foreach (var beatmap in beatmaps)
- {
- ItemsContainer.Add(new DrawableMostPlayedBeatmap(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount));
- }
- });
-
- Api.Queue(request);
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- request?.Cancel();
- }
+ protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap model) =>
+ new DrawableMostPlayedBeatmap(model.GetBeatmapInfo(Rulesets), model.PlayCount);
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs
index b459afcb49..bb221bd43a 100644
--- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs
@@ -11,22 +11,27 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Users;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
namespace osu.Game.Overlays.Profile.Sections
{
- public abstract class PaginatedContainer : FillFlowContainer
+ public abstract class PaginatedContainer : FillFlowContainer
{
- protected readonly FillFlowContainer ItemsContainer;
- protected readonly ShowMoreButton MoreButton;
- protected readonly OsuSpriteText MissingText;
+ private readonly ShowMoreButton moreButton;
+ private readonly OsuSpriteText missingText;
+ private APIRequest> retrievalRequest;
+ private CancellationTokenSource loadCancellation;
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
protected int VisiblePages;
protected int ItemsPerPage;
protected readonly Bindable User = new Bindable();
-
- protected IAPIProvider Api;
- protected APIRequest RetrievalRequest;
+ protected readonly FillFlowContainer ItemsContainer;
protected RulesetStore Rulesets;
protected PaginatedContainer(Bindable user, string header, string missing)
@@ -51,15 +56,15 @@ namespace osu.Game.Overlays.Profile.Sections
RelativeSizeAxes = Axes.X,
Spacing = new Vector2(0, 2),
},
- MoreButton = new ShowMoreButton
+ moreButton = new ShowMoreButton
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Alpha = 0,
Margin = new MarginPadding { Top = 10 },
- Action = ShowMore,
+ Action = showMore,
},
- MissingText = new OsuSpriteText
+ missingText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 15),
Text = missing,
@@ -69,9 +74,8 @@ namespace osu.Game.Overlays.Profile.Sections
}
[BackgroundDependencyLoader]
- private void load(IAPIProvider api, RulesetStore rulesets)
+ private void load(RulesetStore rulesets)
{
- Api = api;
Rulesets = rulesets;
User.ValueChanged += onUserChanged;
@@ -80,13 +84,54 @@ namespace osu.Game.Overlays.Profile.Sections
private void onUserChanged(ValueChangedEvent e)
{
+ loadCancellation?.Cancel();
+ retrievalRequest?.Cancel();
+
VisiblePages = 0;
ItemsContainer.Clear();
if (e.NewValue != null)
- ShowMore();
+ showMore();
}
- protected abstract void ShowMore();
+ private void showMore()
+ {
+ loadCancellation = new CancellationTokenSource();
+
+ retrievalRequest = CreateRequest();
+ retrievalRequest.Success += UpdateItems;
+
+ api.Queue(retrievalRequest);
+ }
+
+ protected virtual void UpdateItems(List items) => Schedule(() =>
+ {
+ if (!items.Any() && VisiblePages == 1)
+ {
+ moreButton.Hide();
+ moreButton.IsLoading = false;
+ missingText.Show();
+ return;
+ }
+
+ LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables =>
+ {
+ missingText.Hide();
+ moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0);
+ moreButton.IsLoading = false;
+
+ ItemsContainer.AddRange(drawables);
+ }, loadCancellation.Token);
+ });
+
+ protected abstract APIRequest> CreateRequest();
+
+ protected abstract Drawable CreateDrawableItem(TModel model);
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ retrievalRequest?.Cancel();
+ }
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
index 4a9ac6e5c7..853b9db0a7 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
@@ -5,18 +5,18 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
using System;
-using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Online.API.Requests.Responses;
+using System.Collections.Generic;
+using osu.Game.Online.API;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
- public class PaginatedScoreContainer : PaginatedContainer
+ public class PaginatedScoreContainer : PaginatedContainer
{
private readonly bool includeWeight;
private readonly ScoreType type;
- private GetUserScoresRequest request;
public PaginatedScoreContainer(ScoreType type, Bindable user, string header, string missing, bool includeWeight = false)
: base(user, header, missing)
@@ -29,52 +29,27 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
ItemsContainer.Direction = FillDirection.Vertical;
}
- protected override void ShowMore()
+ protected override void UpdateItems(List items)
{
- request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
- request.Success += scores => Schedule(() =>
- {
- foreach (var s in scores)
- s.Ruleset = Rulesets.GetRuleset(s.RulesetID);
+ foreach (var item in items)
+ item.Ruleset = Rulesets.GetRuleset(item.RulesetID);
- if (!scores.Any() && VisiblePages == 1)
- {
- MoreButton.Hide();
- MoreButton.IsLoading = false;
- MissingText.Show();
- return;
- }
-
- IEnumerable drawableScores;
-
- switch (type)
- {
- default:
- drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null));
- break;
-
- case ScoreType.Recent:
- drawableScores = scores.Select(score => new DrawableTotalScore(score));
- break;
- }
-
- LoadComponentsAsync(drawableScores, s =>
- {
- MissingText.Hide();
- MoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0);
- MoreButton.IsLoading = false;
-
- ItemsContainer.AddRange(s);
- });
- });
-
- Api.Queue(request);
+ base.UpdateItems(items);
}
- protected override void Dispose(bool isDisposing)
+ protected override APIRequest> CreateRequest() =>
+ new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
+
+ protected override Drawable CreateDrawableItem(APILegacyScoreInfo model)
{
- base.Dispose(isDisposing);
- request?.Cancel();
+ switch (type)
+ {
+ default:
+ return new DrawablePerformanceScore(model, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null);
+
+ case ScoreType.Recent:
+ return new DrawableTotalScore(model);
+ }
}
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs
index f2a778a874..3f9d4dc93e 100644
--- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs
@@ -4,51 +4,24 @@
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
-using System.Linq;
using osu.Framework.Bindables;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.API;
+using System.Collections.Generic;
namespace osu.Game.Overlays.Profile.Sections.Recent
{
- public class PaginatedRecentActivityContainer : PaginatedContainer
+ public class PaginatedRecentActivityContainer : PaginatedContainer
{
- private GetUserRecentActivitiesRequest request;
-
public PaginatedRecentActivityContainer(Bindable user, string header, string missing)
: base(user, header, missing)
{
ItemsPerPage = 5;
}
- protected override void ShowMore()
- {
- request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage);
- request.Success += activities => Schedule(() =>
- {
- MoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0);
- MoreButton.IsLoading = false;
+ protected override APIRequest> CreateRequest() =>
+ new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage);
- if (!activities.Any() && VisiblePages == 1)
- {
- MissingText.Show();
- return;
- }
-
- MissingText.Hide();
-
- foreach (APIRecentActivity activity in activities)
- {
- ItemsContainer.Add(new DrawableRecentActivity(activity));
- }
- });
-
- Api.Queue(request);
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- request?.Cancel();
- }
+ protected override Drawable CreateDrawableItem(APIRecentActivity model) => new DrawableRecentActivity(model);
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs b/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs
index 5ed546c62b..cf4e1c0dde 100644
--- a/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs
+++ b/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs
@@ -124,14 +124,12 @@ namespace osu.Game.Overlays.Profile.Sections
private class ChevronIcon : SpriteIcon
{
- private const int bottom_margin = 2;
private const int icon_size = 8;
public ChevronIcon()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Margin = new MarginPadding { Bottom = bottom_margin };
Size = new Vector2(icon_size);
Icon = FontAwesome.Solid.ChevronDown;
}
diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs
index 7f794e2927..5000156e97 100644
--- a/osu.Game/Overlays/SettingsSubPanel.cs
+++ b/osu.Game/Overlays/SettingsSubPanel.cs
@@ -57,7 +57,6 @@ namespace osu.Game.Overlays
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Y = -15,
Size = new Vector2(15),
Shadow = true,
Icon = FontAwesome.Solid.ChevronLeft
diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs
index a4884dc2c1..6d876a77b1 100644
--- a/osu.Game/Overlays/Volume/MuteButton.cs
+++ b/osu.Game/Overlays/Volume/MuteButton.cs
@@ -65,16 +65,15 @@ namespace osu.Game.Overlays.Volume
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(20),
}
});
- Current.ValueChanged += muted =>
+ Current.BindValueChanged(muted =>
{
icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp;
- };
-
- Current.TriggerChange();
+ icon.Size = new Vector2(muted.NewValue ? 18 : 20);
+ icon.Margin = new MarginPadding { Right = muted.NewValue ? 2 : 0 };
+ }, true);
}
protected override bool OnHover(HoverEvent e)
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 b72a55b9ed..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;
@@ -132,6 +133,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
public event Action ApplyCustomUpdateState;
+#pragma warning disable 618 // (legacy state management) - can be removed 20200227
+
///
/// Enables automatic transform management of this hitobject. Implementation of transforms should be done in and only. Rewinding and removing previous states is done automatically.
///
@@ -139,6 +142,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// Going forward, this is the preferred way of implementing s. Previous functionality
/// is offered as a compatibility layer until all rulesets have been migrated across.
///
+ [Obsolete("Use UpdateInitialTransforms()/UpdateStateTransforms() instead")] // can be removed 20200227
protected virtual bool UseTransformStateManagement => true;
protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}");
@@ -183,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
@@ -190,6 +196,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
protected virtual void UpdateInitialTransforms()
{
+ this.FadeInFromZero();
}
///
@@ -219,10 +226,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// Should generally not be used when is true; use instead.
///
/// The new armed state.
+ [Obsolete("Use UpdateInitialTransforms()/UpdateStateTransforms() instead")] // can be removed 20200227
protected virtual void UpdateState(ArmedState state)
{
}
+#pragma warning restore 618
+
#endregion
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs
index 1d4cdbf04c..3319f30a6f 100644
--- a/osu.Game/Rulesets/Replays/AutoGenerator.cs
+++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs
@@ -3,6 +3,7 @@
using osu.Game.Beatmaps;
using osu.Game.Replays;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Replays
{
@@ -34,5 +35,13 @@ namespace osu.Game.Rulesets.Replays
protected const double KEY_UP_DELAY = 50;
#endregion
+
+ protected virtual HitObject GetNextObject(int currentIndex)
+ {
+ if (currentIndex >= Beatmap.HitObjects.Count - 1)
+ return null;
+
+ return Beatmap.HitObjects[currentIndex + 1];
+ }
}
}
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/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs
index 9a5a4d4acd..8c9e3c94e2 100644
--- a/osu.Game/Rulesets/RulesetConfigCache.cs
+++ b/osu.Game/Rulesets/RulesetConfigCache.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets
public IRulesetConfigManager GetConfigFor(Ruleset ruleset)
{
if (ruleset.RulesetInfo.ID == null)
- throw new InvalidOperationException("The provided ruleset doesn't have a valid id.");
+ return null;
return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore));
}
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 eb14bd1f24..021bd515b5 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -62,13 +62,20 @@ namespace osu.Game.Rulesets.UI
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
+ private bool frameStablePlayback = true;
+
///
/// Whether to enable frame-stable playback.
///
internal bool FrameStablePlayback
{
- get => frameStabilityContainer.FrameStablePlayback;
- set => frameStabilityContainer.FrameStablePlayback = value;
+ get => frameStablePlayback;
+ set
+ {
+ frameStablePlayback = false;
+ if (frameStabilityContainer != null)
+ frameStabilityContainer.FrameStablePlayback = value;
+ }
}
///
@@ -106,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)
@@ -156,6 +163,7 @@ namespace osu.Game.Rulesets.UI
{
frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime)
{
+ FrameStablePlayback = FrameStablePlayback,
Child = KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield)
diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs
index 5bb1de7a38..88a2338b94 100644
--- a/osu.Game/Rulesets/UI/ModIcon.cs
+++ b/osu.Game/Rulesets/UI/ModIcon.cs
@@ -52,7 +52,6 @@ namespace osu.Game.Rulesets.UI
Anchor = Anchor.Centre,
Size = new Vector2(size),
Icon = OsuIcon.ModBg,
- Y = -6.5f,
Shadow = true,
},
modIcon = new SpriteIcon
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/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 19247d8a37..1df8c8218f 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -12,8 +12,13 @@ namespace osu.Game.Rulesets.UI.Scrolling
{
public class ScrollingHitObjectContainer : HitObjectContainer
{
- private readonly IBindable timeRange = new BindableDouble();
+ ///
+ /// A multiplier applied to the length of the scrolling area to determine a safe default lifetime end for hitobjects.
+ /// This is only used to limit the lifetime end within reason, as proper lifetime management should be implemented on hitobjects themselves.
+ ///
+ private const float safe_lifetime_end_multiplier = 2;
+ private readonly IBindable timeRange = new BindableDouble();
private readonly IBindable direction = new Bindable();
[Resolved]
@@ -88,24 +93,29 @@ namespace osu.Game.Rulesets.UI.Scrolling
private void computeInitialStateRecursive(DrawableHitObject hitObject)
{
- hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value);
+ double endTime = hitObject.HitObject.StartTime;
- if (hitObject.HitObject is IHasEndTime endTime)
+ if (hitObject.HitObject is IHasEndTime e)
{
+ endTime = e.EndTime;
+
switch (direction.Value)
{
case ScrollingDirection.Up:
case ScrollingDirection.Down:
- hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength);
+ hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime, timeRange.Value, scrollLength);
break;
case ScrollingDirection.Left:
case ScrollingDirection.Right:
- hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength);
+ hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime, timeRange.Value, scrollLength);
break;
}
}
+ hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value);
+ hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime, timeRange.Value, scrollLength);
+
foreach (var obj in hitObject.NestedHitObjects)
{
computeInitialStateRecursive(obj);
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/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
index f5a72c2c1d..db970dd76e 100644
--- a/osu.Game/Screens/Menu/IntroTriangles.cs
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -138,8 +138,8 @@ namespace osu.Game.Screens.Menu
private RulesetFlow rulesets;
private Container rulesetsScale;
- private Drawable logoContainerSecondary;
- private Drawable logoContainer;
+ private Container logoContainerSecondary;
+ private Drawable lazerLogo;
private GlitchingTriangles triangles;
@@ -158,7 +158,7 @@ namespace osu.Game.Screens.Menu
{
this.game = game;
- InternalChildren = new[]
+ InternalChildren = new Drawable[]
{
triangles = new GlitchingTriangles
{
@@ -191,7 +191,7 @@ namespace osu.Game.Screens.Menu
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Child = logoContainer = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4"))
+ Child = lazerLogo = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4"))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -222,7 +222,7 @@ namespace osu.Game.Screens.Menu
const float scale_adjust = 0.8f;
rulesets.Hide();
- logoContainer.Hide();
+ lazerLogo.Hide();
background.Hide();
using (BeginAbsoluteSequence(0, true))
@@ -269,14 +269,17 @@ namespace osu.Game.Screens.Menu
rulesets.FadeOut();
// matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5
- logoContainer.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint);
+ lazerLogo.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint);
logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad);
}
using (BeginDelayedSequence(logo_2, true))
{
- logoContainer.FadeOut().OnComplete(_ =>
+ lazerLogo.FadeOut().OnComplete(_ =>
{
+ logoContainerSecondary.Remove(lazerLogo);
+ lazerLogo.Dispose(); // explicit disposal as we are pushing a new screen and the expire may not get run.
+
logo.FadeIn();
background.FadeIn();
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
index 4ceb82d4cc..97b6a78804 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
@@ -19,6 +19,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
+using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
@@ -95,10 +96,11 @@ namespace osu.Game.Screens.Select.Carousel
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
Status = beatmapSet.Status
},
- new FillFlowContainer
+ new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Children = ((CarouselBeatmapSet)Item).Beatmaps.Select(b => new FilterableDifficultyIcon(b)).ToList()
+ Spacing = new Vector2(3),
+ ChildrenEnumerable = getDifficultyIcons(),
},
}
}
@@ -107,6 +109,17 @@ namespace osu.Game.Screens.Select.Carousel
};
}
+ private const int maximum_difficulty_icons = 18;
+
+ private IEnumerable getDifficultyIcons()
+ {
+ var beatmaps = ((CarouselBeatmapSet)Item).Beatmaps.ToList();
+
+ return beatmaps.Count > maximum_difficulty_icons
+ ? (IEnumerable)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key))
+ : beatmaps.Select(b => new FilterableDifficultyIcon(b));
+ }
+
public MenuItem[] ContextMenuItems
{
get
@@ -204,5 +217,27 @@ namespace osu.Game.Screens.Select.Carousel
filtered.TriggerChange();
}
}
+
+ public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon
+ {
+ private readonly List items;
+
+ public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset)
+ : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White)
+ {
+ this.items = items;
+
+ foreach (var item in items)
+ item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay));
+
+ updateFilteredDisplay();
+ }
+
+ private void updateFilteredDisplay()
+ {
+ // for now, fade the whole group based on the ratio of hidden items.
+ this.FadeTo(1 - 0.9f * ((float)items.Count(i => i.Filtered.Value) / items.Count), 100);
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index 84e8e90f54..ed74b01fc9 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -29,40 +29,14 @@ namespace osu.Game.Screens.Select
private readonly TabControl groupTabs;
- private SortMode sort = SortMode.Title;
+ private Bindable sortMode;
- public SortMode Sort
- {
- get => sort;
- set
- {
- if (sort != value)
- {
- sort = value;
- FilterChanged?.Invoke(CreateCriteria());
- }
- }
- }
-
- private GroupMode group = GroupMode.All;
-
- public GroupMode Group
- {
- get => group;
- set
- {
- if (group != value)
- {
- group = value;
- FilterChanged?.Invoke(CreateCriteria());
- }
- }
- }
+ private Bindable groupMode;
public FilterCriteria CreateCriteria() => new FilterCriteria
{
- Group = group,
- Sort = sort,
+ Group = groupMode.Value,
+ Sort = sortMode.Value,
SearchText = searchTextBox.Text,
AllowConvertedBeatmaps = showConverted.Value,
Ruleset = ruleset.Value
@@ -122,7 +96,6 @@ namespace osu.Game.Screens.Select
Height = 24,
Width = 0.5f,
AutoSort = true,
- Current = { Value = GroupMode.Title }
},
//spriteText = new OsuSpriteText
//{
@@ -141,7 +114,6 @@ namespace osu.Game.Screens.Select
Width = 0.5f,
Height = 24,
AutoSort = true,
- Current = { Value = SortMode.Title }
}
}
},
@@ -153,8 +125,6 @@ namespace osu.Game.Screens.Select
groupTabs.PinItem(GroupMode.All);
groupTabs.PinItem(GroupMode.RecentlyPlayed);
- groupTabs.Current.ValueChanged += group => Group = group.NewValue;
- sortTabs.Current.ValueChanged += sort => Sort = sort.NewValue;
}
public void Deactivate()
@@ -184,7 +154,18 @@ namespace osu.Game.Screens.Select
showConverted.ValueChanged += _ => updateCriteria();
ruleset.BindTo(parentRuleset);
- ruleset.BindValueChanged(_ => updateCriteria(), true);
+ ruleset.BindValueChanged(_ => updateCriteria());
+
+ sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode);
+ groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode);
+
+ sortTabs.Current.BindTo(sortMode);
+ groupTabs.Current.BindTo(groupMode);
+
+ groupMode.BindValueChanged(_ => updateCriteria());
+ sortMode.BindValueChanged(_ => updateCriteria());
+
+ updateCriteria();
}
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
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/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index c7556dddd5..6072bb64ed 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -4,6 +4,7 @@
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
namespace osu.Game.Skinning
{
@@ -19,6 +20,6 @@ namespace osu.Game.Skinning
public override Texture GetTexture(string componentName) => null;
- public override SampleChannel GetSample(string sampleName) => null;
+ public override SampleChannel GetSample(ISampleInfo sampleInfo) => null;
}
}
diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs
index 0e67a1897c..4867aba0a9 100644
--- a/osu.Game/Skinning/ISkin.cs
+++ b/osu.Game/Skinning/ISkin.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
namespace osu.Game.Skinning
{
@@ -17,7 +18,7 @@ namespace osu.Game.Skinning
Texture GetTexture(string componentName);
- SampleChannel GetSample(string sampleName);
+ SampleChannel GetSample(ISampleInfo sampleInfo);
TValue GetValue(Func query) where TConfiguration : SkinConfiguration;
}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 48310cf027..0cc5e9c9b6 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Text;
+using osu.Game.Audio;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -160,6 +161,50 @@ namespace osu.Game.Skinning
return getAnimation(componentName, animatable, looping);
}
+ public override Texture GetTexture(string componentName)
+ {
+ componentName = getFallbackName(componentName);
+
+ float ratio = 2;
+ var texture = Textures.Get($"{componentName}@2x");
+
+ if (texture == null)
+ {
+ ratio = 1;
+ texture = Textures.Get(componentName);
+ }
+
+ if (texture != null)
+ texture.ScaleAdjust = ratio;
+
+ return texture;
+ }
+
+ public override SampleChannel GetSample(ISampleInfo sampleInfo)
+ {
+ foreach (var lookup in sampleInfo.LookupNames)
+ {
+ var sample = Samples.Get(getFallbackName(lookup));
+
+ if (sample != null)
+ return sample;
+ }
+
+ if (sampleInfo is HitSampleInfo hsi)
+ // Try fallback to non-bank samples.
+ return Samples.Get(hsi.Name);
+
+ return null;
+ }
+
+ private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null;
+
+ private string getFallbackName(string componentName)
+ {
+ string lastPiece = componentName.Split('/').Last();
+ return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece;
+ }
+
private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-")
{
Texture texture;
@@ -195,27 +240,6 @@ namespace osu.Game.Skinning
return null;
}
- public override Texture GetTexture(string componentName)
- {
- float ratio = 2;
- var texture = Textures.Get($"{componentName}@2x");
-
- if (texture == null)
- {
- ratio = 1;
- texture = Textures.Get(componentName);
- }
-
- if (texture != null)
- texture.ScaleAdjust = ratio;
-
- return texture;
- }
-
- public override SampleChannel GetSample(string sampleName) => Samples.Get(sampleName);
-
- private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null;
-
protected class LegacySkinResourceStore : IResourceStore
where T : INamedFileInfo
{
@@ -224,13 +248,13 @@ namespace osu.Game.Skinning
private string getPathForFile(string filename)
{
+ if (source.Files == null)
+ return null;
+
bool hasExtension = filename.Contains('.');
- string lastPiece = filename.Split('/').Last();
- var legacyName = filename.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece;
-
var file = source.Files.Find(f =>
- string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), legacyName, StringComparison.InvariantCultureIgnoreCase));
+ string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase));
return file?.FileInfo.StoragePath;
}
diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs
index 7882e0f31b..fc36d1c8da 100644
--- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs
+++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs
@@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
using osu.Game.Configuration;
namespace osu.Game.Skinning
@@ -49,13 +50,13 @@ namespace osu.Game.Skinning
return fallbackSource.GetTexture(componentName);
}
- public SampleChannel GetSample(string sampleName)
+ public SampleChannel GetSample(ISampleInfo sampleInfo)
{
SampleChannel sourceChannel;
- if (beatmapHitsounds.Value && (sourceChannel = skin?.GetSample(sampleName)) != null)
+ if (beatmapHitsounds.Value && (sourceChannel = skin?.GetSample(sampleInfo)) != null)
return sourceChannel;
- return fallbackSource?.GetSample(sampleName);
+ return fallbackSource?.GetSample(sampleInfo);
}
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index 09c0d3d0bc..027d9df8b8 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
namespace osu.Game.Skinning
{
@@ -16,7 +17,7 @@ namespace osu.Game.Skinning
public abstract Drawable GetDrawableComponent(string componentName);
- public abstract SampleChannel GetSample(string sampleName);
+ public abstract SampleChannel GetSample(ISampleInfo sampleInfo);
public abstract Texture GetTexture(string componentName);
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 19997e8844..a713933c6e 100644
--- a/osu.Game/Skinning/SkinManager.cs
+++ b/osu.Game/Skinning/SkinManager.cs
@@ -14,7 +14,9 @@ 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;
using osu.Game.IO.Archives;
@@ -24,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 };
@@ -33,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 =>
{
@@ -65,6 +70,7 @@ namespace osu.Game.Skinning
{
var userSkins = GetAllUserSkins();
userSkins.Insert(0, SkinInfo.Default);
+ userSkins.Insert(1, DefaultLegacySkin.Info);
return userSkins;
}
@@ -90,7 +96,7 @@ namespace osu.Game.Skinning
else
{
model.Name = model.Name.Replace(".osk", "");
- model.Creator = "Unknown";
+ model.Creator = model.Creator ?? "Unknown";
}
}
@@ -104,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);
}
@@ -120,7 +129,7 @@ namespace osu.Game.Skinning
public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
- public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName);
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => CurrentSkin.Value.GetValue(query);
}
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index 8e2b5cec98..dcb14b4412 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.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 System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -37,34 +36,26 @@ namespace osu.Game.Skinning
public void Play() => channels?.ForEach(c => c.Play());
- public override bool IsPresent => false; // We don't need to receive updates.
+ public override bool IsPresent => Scheduler.HasPendingTasks;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
channels = hitSamples.Select(s =>
{
- var ch = loadChannel(s, skin.GetSample);
+ var ch = skin.GetSample(s);
+
if (ch == null && allowFallback)
- ch = loadChannel(s, audio.Samples.Get);
+ foreach (var lookup in s.LookupNames)
+ if ((ch = audio.Samples.Get($"Gameplay/{lookup}")) != null)
+ break;
+
+ if (ch != null)
+ ch.Volume.Value = s.Volume / 100.0;
+
return ch;
}).Where(c => c != null).ToArray();
}
- private SampleChannel loadChannel(ISampleInfo info, Func