diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 08ac55508a..a06435583b 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -4,6 +4,7 @@
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
switch (targetComponent.Lookup)
{
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var components = base.GetDrawableComponent(lookup) as SkinnableTargetComponentsContainer;
+ var components = base.GetDrawableComponent(lookup) as Container;
if (providesComboCounter && components != null)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 264a1bd5ec..25d0573a82 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
private double? releaseTime;
- public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
-
public DrawableHoldNote()
: this(null)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
index bf477277c6..20ea962994 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -15,13 +15,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
public partial class DrawableHoldNoteTail : DrawableNote
{
- ///
- /// Lenience of release hit windows. This is to make cases where the hold note release
- /// is timed alongside presses of other hit objects less awkward.
- /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
- ///
- private const double release_window_lenience = 1.5;
-
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
@@ -40,14 +33,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true);
- public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
-
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
// Factor in the release lenience
- timeOffset /= release_window_lenience;
+ timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE;
if (!userTriggered)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 22fab15c1b..c367886efe 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Mania.Objects
///
public TailNote Tail { get; private set; }
+ public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
+
///
/// The time between ticks of this hold.
///
diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs
index cda8e2fa31..d6dc25079a 100644
--- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs
@@ -10,6 +10,15 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public class TailNote : Note
{
+ ///
+ /// Lenience of release hit windows. This is to make cases where the hold note release
+ /// is timed alongside presses of other hit objects less awkward.
+ /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
+ ///
+ public const double RELEASE_WINDOW_LENIENCE = 1.5;
+
public override Judgement CreateJudgement() => new ManiaJudgement();
+
+ public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
index b9ce07363c..34253e3d4f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
@@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre;
}
- public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
-
///
/// Apply a judgement result.
///
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 0e1fe56cea..ed6f8a9a6a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -71,8 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
AddNested(i < SpinsRequired
- ? new SpinnerTick { StartTime = startTime }
- : new SpinnerBonusTick { StartTime = startTime });
+ ? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
+ : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration });
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
index 650d02c675..c890f3771b 100644
--- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
@@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class SpinnerTick : OsuHitObject
{
+ ///
+ /// Duration of the containing this spinner tick.
+ ///
+ public double SpinnerDuration { get; set; }
+
public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+ public override double MaximumJudgementOffset => SpinnerDuration;
+
public class OsuSpinnerTickJudgement : OsuJudgement
{
public override HitResult MaxResult => HitResult.SmallBonus;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index 45e25ee7dc..abecd19545 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), _ => new TickPiece());
- public override double MaximumJudgementOffset => HitObject.HitWindow;
-
protected override void OnApply()
{
base.OnApply();
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
index 433fdab908..6bcb8674e6 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
@@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+ public override double MaximumJudgementOffset => HitWindow;
+
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
public class StrongNestedHit : StrongNestedHitObject
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
index 5cd8c00935..94cb8517e9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -1,13 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Lists;
using osu.Framework.Testing;
using osu.Framework.Timing;
@@ -28,10 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
{
- private ISkin currentBeatmapSkin;
+ private ISkin currentBeatmapSkin = null!;
[Resolved]
- private SkinManager skinManager { get; set; }
+ private SkinManager skinManager { get; set; } = null!;
protected override bool HasCustomSteps => true;
@@ -57,15 +56,15 @@ namespace osu.Game.Tests.Visual.Gameplay
protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource)
{
- var actualComponentsContainer = Player.ChildrenOfType().First(s => s.Target == target)
- .ChildrenOfType().SingleOrDefault();
+ var targetContainer = Player.ChildrenOfType().First(s => s.Target == target);
+ var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer);
if (actualComponentsContainer == null)
return false;
var actualInfo = actualComponentsContainer.CreateSkinnableInfo();
- var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target));
+ var expectedComponentsContainer = expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)) as Container;
if (expectedComponentsContainer == null)
return false;
@@ -92,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay
return almostEqual(actualInfo, expectedInfo);
}
- private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
+ private static bool almostEqual(SkinnableInfo info, SkinnableInfo? other) =>
other != null
&& info.Type == other.Type
&& info.Anchor == other.Anchor
@@ -102,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay
&& Precision.AlmostEquals(info.Rotation, other.Rotation)
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual));
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
@@ -111,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private readonly ISkin beatmapSkin;
- public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
+ public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
: base(beatmap, storyboard, referenceClock, audio)
{
this.beatmapSkin = beatmapSkin;
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 39f0888882..40b51ddcca 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -650,18 +650,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
UpdateResult(false);
}
- ///
- /// The maximum offset from the end time of at which this can be judged.
- /// The time offset of will be clamped to this value during .
- ///
- /// Defaults to the miss window of .
- ///
- ///
- ///
- /// This does not affect the time offset provided to invocations of .
- ///
- public virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0;
-
///
/// Applies the of this , notifying responders such as
/// the of the .
@@ -683,7 +671,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
$"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}]).");
}
- Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime());
+ Result.TimeOffset = Math.Min(HitObject.MaximumJudgementOffset, Time.Current - HitObject.GetEndTime());
if (Result.HasResult)
updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss);
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 0f79e58201..25f538d211 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -200,6 +200,14 @@ namespace osu.Game.Rulesets.Objects
[NotNull]
protected virtual HitWindows CreateHitWindows() => new HitWindows();
+ ///
+ /// The maximum offset from the end time of at which this can be judged.
+ ///
+ /// Defaults to the miss window.
+ ///
+ ///
+ public virtual double MaximumJudgementOffset => HitWindows?.WindowFor(HitResult.Miss) ?? 0;
+
public IList CreateSlidingSamples()
{
var slidingSamples = new List();
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 3559a1521c..b93a427196 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -232,8 +232,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
double computedStartTime = computeDisplayStartTime(entry);
// always load the hitobject before its first judgement offset
- double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0;
- entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime);
+ entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - entry.HitObject.MaximumJudgementOffset, computedStartTime);
}
private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null)
diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs
index 53c6c1e5ce..71f0888c6f 100644
--- a/osu.Game/Skinning/ArgonSkin.cs
+++ b/osu.Game/Skinning/ArgonSkin.cs
@@ -94,7 +94,7 @@ namespace osu.Game.Skinning
switch (globalLookup.Lookup)
{
case GlobalSkinComponentLookup.LookupType.SongSelect:
- var songSelectComponents = new SkinnableTargetComponentsContainer(_ =>
+ var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
{
// do stuff when we need to.
});
@@ -102,7 +102,7 @@ namespace osu.Game.Skinning
return songSelectComponents;
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
+ var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
diff --git a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs b/osu.Game/Skinning/DefaultSkinComponentsContainer.cs
similarity index 61%
rename from osu.Game/Skinning/SkinnableTargetComponentsContainer.cs
rename to osu.Game/Skinning/DefaultSkinComponentsContainer.cs
index 8c6726c3f4..0d6e6b6ce9 100644
--- a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs
+++ b/osu.Game/Skinning/DefaultSkinComponentsContainer.cs
@@ -2,39 +2,28 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using Newtonsoft.Json;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning
{
///
- /// A container which groups the components of a into a single object.
- /// Optionally also applies a default layout to the components.
+ /// A container which can be used to specify default skin components layouts.
+ /// Handles applying a default layout to the components.
///
- [Serializable]
- public partial class SkinnableTargetComponentsContainer : Container, ISkinnableDrawable
+ public partial class DefaultSkinComponentsContainer : Container
{
- public bool IsEditable => false;
-
- public bool UsesFixedAnchor { get; set; }
-
private readonly Action? applyDefaults;
///
/// Construct a wrapper with defaults that should be applied once.
///
/// A function to apply the default layout.
- public SkinnableTargetComponentsContainer(Action applyDefaults)
- : this()
- {
- this.applyDefaults = applyDefaults;
- }
-
- [JsonConstructor]
- public SkinnableTargetComponentsContainer()
+ public DefaultSkinComponentsContainer(Action applyDefaults)
{
RelativeSizeAxes = Axes.Both;
+
+ this.applyDefaults = applyDefaults;
}
protected override void LoadComplete()
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index b2619fa55b..284a938bce 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -347,7 +347,7 @@ namespace osu.Game.Skinning
switch (target.Lookup)
{
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
+ var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index 37e98946c9..c55b4e789c 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -11,6 +11,7 @@ using Newtonsoft.Json;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
@@ -174,8 +175,9 @@ namespace osu.Game.Skinning
foreach (var i in skinnableInfo)
components.Add(i.CreateInstance());
- return new SkinnableTargetComponentsContainer
+ return new Container
{
+ RelativeSizeAxes = Axes.Both,
Children = components,
};
}
diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs
index df5299d427..b21e51c0cf 100644
--- a/osu.Game/Skinning/SkinnableTargetContainer.cs
+++ b/osu.Game/Skinning/SkinnableTargetContainer.cs
@@ -7,13 +7,14 @@ using System.Linq;
using System.Threading;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning
{
public partial class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget
{
- private SkinnableTargetComponentsContainer? content;
+ private Container? content;
public GlobalSkinComponentLookup.LookupType Target { get; }
@@ -39,15 +40,16 @@ namespace osu.Game.Skinning
foreach (var i in skinnableInfo)
drawables.Add(i.CreateInstance());
- Reload(new SkinnableTargetComponentsContainer
+ Reload(new Container
{
+ RelativeSizeAxes = Axes.Both,
Children = drawables,
});
}
- public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as SkinnableTargetComponentsContainer);
+ public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as Container);
- public void Reload(SkinnableTargetComponentsContainer? componentsContainer)
+ public void Reload(Container? componentsContainer)
{
ClearInternal();
components.Clear();
diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs
index 62ef94691b..7de2b124e7 100644
--- a/osu.Game/Skinning/TrianglesSkin.cs
+++ b/osu.Game/Skinning/TrianglesSkin.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Skinning
switch (target.Lookup)
{
case GlobalSkinComponentLookup.LookupType.SongSelect:
- var songSelectComponents = new SkinnableTargetComponentsContainer(_ =>
+ var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
{
// do stuff when we need to.
});
@@ -80,7 +80,7 @@ namespace osu.Game.Skinning
return songSelectComponents;
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
+ var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();